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