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.util.ArrayList;
032    import java.util.Calendar;
033    import java.util.Date;
034    import java.util.Hashtable;
035    import java.util.List;
036    
037    import ucar.unidata.data.NamedArray;
038    import ucar.unidata.geoloc.LatLonPointImpl;
039    import ucar.unidata.geoloc.LatLonRect;
040    import ucar.unidata.util.Misc;
041    import ucar.visad.Util;
042    import visad.CommonUnit;
043    import visad.DateTime;
044    import visad.Real;
045    import visad.RealType;
046    import visad.VisADException;
047    import visad.georef.EarthLocation;
048    
049    /**
050     * Created by IntelliJ IDEA. User: yuanho Date: Apr 9, 2008 Time: 5:00:17 PM To
051     * change this template use File | Settings | File Templates.
052     */
053    
054    public class StormTrack implements Comparable {
055    
056            /** _more_ */
057            private List<StormParam> params = null;
058    
059            /** _more_ */
060            private LatLonRect bbox;
061    
062            /** _more_ */
063            private String trackId;
064    
065            /** _more_ */
066            private StormInfo stormInfo;
067    
068            /** _more_ */
069            private Way way;
070    
071            /** _more_ */
072            private NamedArray lats;
073    
074            /** _more_ */
075            private NamedArray lons;
076    
077            /** _more_ */
078            private List<StormTrackPoint> trackPoints;
079    
080            // private Date trackStartTime;
081    
082            private Hashtable temporaryProperties = new Hashtable();
083    
084            private static final int DIAMOND_MISSING_VALUE = 9999;
085    
086            private boolean isEdited = false;
087    
088            /**
089             * _more_
090             * 
091             * @param track
092             *            _more_
093             */
094            public StormTrack(StormTrack track) {
095                    this.stormInfo = track.stormInfo;
096                    this.way = track.way;
097                    this.params = track.params;
098                    this.trackId = track.trackId;
099                    this.trackPoints = new ArrayList<StormTrackPoint>(track.trackPoints);
100            }
101    
102            /**
103             * _more_
104             * 
105             * @param stormInfo
106             *            _more_
107             * @param way
108             *            _more_
109             * @param pts
110             *            _more_
111             * @param params
112             *            _more_
113             */
114            public StormTrack(StormInfo stormInfo, Way way, List<StormTrackPoint> pts,
115                            StormParam[] params) {
116                    this.stormInfo = stormInfo;
117                    this.way = way;
118                    if (params != null) {
119                            this.params = (List<StormParam>) Misc.toList(params);
120                    }
121                    this.trackPoints = new ArrayList<StormTrackPoint>(pts);
122                    StormTrackPoint firstPoint = (StormTrackPoint) pts.get(0);
123                    DateTime trackStartTime = firstPoint.getTime();
124                    this.trackId = stormInfo.toString() + "_" + way + "_"
125                                    + trackStartTime.getValue();
126            }
127    
128            /**
129             * _more_
130             * 
131             * @param stormInfo
132             *            _more_
133             * @param way
134             *            _more_
135             * @param startTime
136             *            _more_
137             * @param params
138             *            _more_
139             */
140            public StormTrack(StormInfo stormInfo, Way way, DateTime startTime,
141                            StormParam[] params) {
142                    this.stormInfo = stormInfo;
143                    this.way = way;
144                    if (params != null) {
145                            this.params = (List<StormParam>) Misc.toList(params);
146                    }
147                    this.trackPoints = new ArrayList();
148                    this.trackId = stormInfo.toString() + "_" + way + "_"
149                                    + startTime.getValue();
150            }
151    
152            /**
153             * _more_
154             * 
155             * @return _more_
156             */
157            public LatLonRect getBoundingBox() {
158                    if (trackPoints.size() == 0) {
159                            return null;
160                    }
161                    if (bbox == null) {
162                            // public LatLonRect(LatLonPoint left, LatLonPoint right) {
163                            double minLon = Double.POSITIVE_INFINITY;
164                            double maxLon = Double.NEGATIVE_INFINITY;
165                            double minLat = Double.POSITIVE_INFINITY;
166                            double maxLat = Double.NEGATIVE_INFINITY;
167                            for (StormTrackPoint stp : trackPoints) {
168                                    EarthLocation el = stp.getLocation();
169                                    minLat = Math.min(minLat, el.getLatitude().getValue());
170                                    maxLat = Math.max(maxLat, el.getLatitude().getValue());
171                                    minLon = Math.min(minLon, el.getLongitude().getValue());
172                                    maxLon = Math.max(maxLon, el.getLongitude().getValue());
173                            }
174    
175                            bbox = new LatLonRect(new LatLonPointImpl(maxLat, minLon),
176                                            new LatLonPointImpl(minLat, maxLon));
177                    }
178                    return bbox;
179            }
180    
181            /**
182             * _more_
183             * 
184             * @param o
185             *            _more_
186             * 
187             * @return _more_
188             */
189            public int compareTo(Object o) {
190                    if (o instanceof StormTrack) {
191                            StormTrack that = (StormTrack) o;
192    
193                            double v1 = getStartTime().getValue();
194                            double v2 = that.getStartTime().getValue();
195                            if (v1 < v2) {
196                                    return -1;
197                            }
198                            if (v1 > v2) {
199                                    return 1;
200                            }
201                            return 0;
202                    }
203                    return toString().compareTo(o.toString());
204            }
205    
206            /**
207             * _more_
208             * 
209             * @param hour
210             *            _more_
211             * 
212             * @return _more_
213             */
214            public StormTrackPoint findPointWithForecastHour(int hour) {
215                    for (StormTrackPoint stp : trackPoints) {
216                            if (stp.getForecastHour() == hour) {
217                                    return stp;
218                            }
219                    }
220                    return null;
221            }
222    
223            /**
224             * _more_
225             * 
226             * @param point
227             *            _more_
228             */
229            public void addPoint(StormTrackPoint point) {
230                    trackPoints.add(point);
231            }
232    
233            /**
234             * _more_
235             * 
236             * @return _more_
237             */
238            public boolean isObservation() {
239                    return way.isObservation();
240            }
241    
242            /**
243             * _more_
244             * 
245             * @return _more_
246             */
247            public boolean isEdited() {
248                    return isEdited;
249            }
250    
251            /**
252             * _more_
253             * 
254             * @return _more_
255             */
256            public void setIsEdited(boolean isEdited) {
257                    this.isEdited = isEdited;
258            }
259    
260            /**
261             * _more_
262             * 
263             * @return _more_
264             */
265            public boolean getIsEdited() {
266                    return this.isEdited;
267            }
268    
269            /**
270             * _more_
271             * 
272             * @return _more_
273             */
274            public int hashCode() {
275                    return trackId.hashCode();
276            }
277    
278            /**
279             * _more_
280             * 
281             * @param id
282             *            _more_
283             */
284            public void setId(String id) {
285                    this.trackId = id;
286            }
287    
288            /**
289             * _more_
290             * 
291             * @return _more_
292             */
293            public String getId() {
294                    return trackId;
295            }
296    
297            /**
298             * _more_
299             * 
300             * @return _more_
301             */
302            public DateTime getStartTime() {
303                    StormTrackPoint firstPoint = trackPoints.get(0);
304                    return firstPoint.getTime();
305    
306            }
307    
308            /**
309             * _more_
310             * 
311             * @param stormInfo
312             *            _more_
313             */
314            public void setStormInfo(StormInfo stormInfo) {
315                    this.stormInfo = stormInfo;
316            }
317    
318            /**
319             * _more_
320             * 
321             * @return _more_
322             */
323            public StormInfo getStormInfo() {
324                    return stormInfo;
325            }
326    
327            /**
328             * _more_
329             * 
330             * @param way
331             *            _more_
332             */
333            public void setWay(Way way) {
334                    this.way = way;
335            }
336    
337            /**
338             * _more_
339             * 
340             * @return _more_
341             */
342            public Way getWay() {
343                    return way;
344            }
345    
346            /**
347             * _more_
348             * 
349             * @param pts
350             *            _more_
351             */
352            public void setTrackPoints(List<StormTrackPoint> pts) {
353                    this.trackPoints = new ArrayList<StormTrackPoint>(pts);
354            }
355    
356            /**
357             * _more_
358             * 
359             * @return _more_
360             */
361            public List<StormTrackPoint> getTrackPoints() {
362                    return trackPoints;
363            }
364    
365            /**
366             * _more_
367             * 
368             * @return _more_
369             */
370            public List<DateTime> getTrackTimes() {
371                    List<DateTime> trackTimes = new ArrayList();
372                    for (StormTrackPoint stp : trackPoints) {
373                            trackTimes.add(stp.getTime());
374                    }
375                    return trackTimes;
376            }
377    
378            /**
379             * _more_
380             * 
381             * @return _more_
382             */
383            public List<StormParam> getParams() {
384                    if (params == null) {
385                            params = new ArrayList<StormParam>();
386                            Hashtable seenParam = new Hashtable();
387                            for (StormTrackPoint stp : trackPoints) {
388                                    List<Real> reals = stp.getTrackAttributes();
389                                    for (Real r : reals) {
390                                            RealType type = (RealType) r.getType();
391                                            if (seenParam.get(type) == null) {
392                                                    seenParam.put(type, type);
393                                                    params.add(new StormParam(type));
394                                            }
395                                    }
396                            }
397                    }
398    
399                    return params;
400            }
401    
402            /**
403             * _more_
404             * 
405             * @return _more_
406             */
407            public List<EarthLocation> getLocations() {
408                    List<EarthLocation> locs = new ArrayList();
409                    for (StormTrackPoint stp : trackPoints) {
410                            locs.add(stp.getLocation());
411                    }
412                    return locs;
413            }
414    
415            /**
416             * _more_
417             * 
418             * 
419             * 
420             * @param param
421             *            _more_
422             * @return _more_
423             * 
424             * @throws VisADException
425             *             _more_
426             */
427            public Real[] getTrackAttributeValues(StormParam param)
428                            throws VisADException {
429                    if (param == null) {
430                            return null;
431                    }
432                    int size = trackPoints.size();
433                    Real[] trackAttributes = new Real[size];
434                    Real missing = null;
435                    for (int i = 0; i < size; i++) {
436                            Real value = trackPoints.get(i).getAttribute(param);
437                            if (value == null) {
438                                    if (i == 0) {
439                                            return null;
440                                    }
441                                    trackAttributes[i] = null;
442                            } else {
443                                    if (missing == null) {
444                                            missing = value.cloneButValue(Double.NaN);
445                                    }
446                                    trackAttributes[i] = value;
447                            }
448                    }
449                    for (int i = 0; i < size; i++) {
450                            if (trackAttributes[i] == null) {
451                                    trackAttributes[i] = missing;
452                            }
453                    }
454                    return trackAttributes;
455            }
456    
457            /**
458             * _more_
459             * 
460             * @param trackAttributes
461             *            _more_
462             * @param i
463             *            _more_
464             * 
465             * @return _more_
466             */
467            public float findClosestAttr(float[] trackAttributes, int i) {
468                    int up = i;
469                    int down = i;
470                    int size = trackAttributes.length;
471                    float value = Float.NaN;
472                    while (Float.isNaN(value)) {
473                            up++;
474                            down--;
475                            if ((up > 0) && (up < size)) {
476                                    value = trackAttributes[up];
477                            }
478                            if ((down > 0) && (down < size)) {
479                                    value = trackAttributes[down];
480                            }
481                    }
482                    return value;
483            }
484    
485            /**
486             * _more_
487             * 
488             * @return _more_
489             */
490            public String toString() {
491                    return trackId;
492            }
493    
494            /**
495             * Return the index of the given track point. This kist finds the point with
496             * the same lat/lon
497             * 
498             * @param stp
499             *            The track point
500             * @return The index or -1 if not found
501             */
502            public int indexOf(StormTrackPoint stp) {
503                    for (int i = 0; i < trackPoints.size(); i++) {
504                            if (trackPoints.get(i).getLocation().equals(stp.getLocation())) {
505                                    return i;
506                            }
507                    }
508                    return -1;
509            }
510    
511            /**
512             * _more_
513             * 
514             * @param o
515             *            _more_
516             * 
517             * @return _more_
518             */
519            public boolean equals(Object o) {
520                    if (o == null) {
521                            return false;
522                    }
523                    if (!(o instanceof StormTrack)) {
524                            return false;
525                    }
526                    StormTrack other = (StormTrack) o;
527                    return ((trackId.equals(other.trackId)));
528            }
529    
530            public void putTemporaryProperty(Object key, Object value) {
531                    temporaryProperties.put(key, value);
532            }
533    
534            public Object getTemporaryProperty(Object key) {
535                    return temporaryProperties.get(key);
536            }
537    
538            static public StringBuffer toDiamond7(List<StormTrack> sts, String id)
539                            throws VisADException {
540                    StringBuffer sb = new StringBuffer();
541                    sb.append("diamond 7 " + id + "TropicalCycloneTrack" + "\n");
542                    for (StormTrack st : sts) {
543                            st.toDiamond7(sb, id);
544                    }
545                    return sb;
546            }
547    
548            public void toDiamond7(StringBuffer sb, String id) throws VisADException {
549                    Calendar cal = Calendar.getInstance();
550                    List<StormTrackPoint> tpoints = getTrackPoints();
551    
552                    sb.append("Name " + id + " " + way + " " + tpoints.size() + "\n");
553                    for (StormTrackPoint stp : tpoints) {
554                            Date dttm = null;
555    
556                            try {
557                                    dttm = Util.makeDate(stp.getTime());
558                            } catch (Exception excp) {
559    
560                            }
561                            cal.setTime(dttm);
562                            String year = Integer.toString(cal.get(Calendar.YEAR));
563                            int mm = cal.get(Calendar.MONTH);
564                            String mon = Integer.toString(mm);
565                            if (mm < 10)
566                                    mon = "0" + mon;
567                            int dd = cal.get(Calendar.DAY_OF_MONTH);
568                            String day = Integer.toString(dd);
569                            if (dd < 10)
570                                    day = "0" + day;
571                            int hour = cal.get(Calendar.HOUR_OF_DAY);
572                            int fhour = stp.getForecastHour();
573                            EarthLocation el = stp.getLocation();
574                            List<Real> attrs = stp.getTrackAttributes();
575    
576                            sb.append(year.substring(2));
577                            sb.append(" ");
578                            sb.append(mon);
579                            sb.append(" ");
580                            sb.append(day);
581                            sb.append(" ");
582                            sb.append(hour);
583                            sb.append(" ");
584                            sb.append(fhour);
585                            sb.append(" ");
586                            sb.append(el.getLongitude().getValue(CommonUnit.degree));
587                            sb.append(" ");
588                            sb.append(el.getLatitude().getValue(CommonUnit.degree));
589                            sb.append(" ");
590    
591                            // TODO: What to do with units?
592                            appendDiamondValue(sb, stp
593                                            .getAttribute(STIStormDataSource.PARAM_MAXWINDSPEED));
594                            appendDiamondValue(sb, stp
595                                            .getAttribute(STIStormDataSource.PARAM_MINPRESSURE));
596                            appendDiamondValue(sb, stp
597                                            .getAttribute(STIStormDataSource.PARAM_RADIUSMODERATEGALE));
598                            appendDiamondValue(sb, stp
599                                            .getAttribute(STIStormDataSource.PARAM_RADIUSWHOLEGALE));
600                            appendDiamondValue(sb, stp
601                                            .getAttribute(STIStormDataSource.PARAM_MOVESPEED));
602                            appendDiamondValue(sb, stp
603                                            .getAttribute(STIStormDataSource.PARAM_MOVEDIRECTION));
604    
605                            sb.append("\n");
606                    }
607            }
608    
609            private void appendDiamondValue(StringBuffer sb, Real r) {
610                    if (r == null || Double.isNaN(r.getValue()))
611                            sb.append(DIAMOND_MISSING_VALUE);
612                    else
613                            sb.append(r.getValue());
614                    sb.append(" ");
615            }
616    
617    }