001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.data.cyclone;
030
031import java.util.ArrayList;
032import java.util.Calendar;
033import java.util.Date;
034import java.util.Hashtable;
035import java.util.List;
036
037import ucar.unidata.data.NamedArray;
038import ucar.unidata.geoloc.LatLonPointImpl;
039import ucar.unidata.geoloc.LatLonRect;
040import ucar.unidata.util.Misc;
041import ucar.visad.Util;
042import visad.CommonUnit;
043import visad.DateTime;
044import visad.Real;
045import visad.RealType;
046import visad.VisADException;
047import 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
054public 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        public void setIsEdited(boolean isEdited) {
255                this.isEdited = isEdited;
256        }
257
258        /**
259         * _more_
260         * 
261         * @return _more_
262         */
263        public boolean getIsEdited() {
264                return this.isEdited;
265        }
266
267        /**
268         * _more_
269         * 
270         * @return _more_
271         */
272        public int hashCode() {
273                return trackId.hashCode();
274        }
275
276        /**
277         * _more_
278         * 
279         * @param id
280         *            _more_
281         */
282        public void setId(String id) {
283                this.trackId = id;
284        }
285
286        /**
287         * _more_
288         * 
289         * @return _more_
290         */
291        public String getId() {
292                return trackId;
293        }
294
295        /**
296         * _more_
297         * 
298         * @return _more_
299         */
300        public DateTime getStartTime() {
301                StormTrackPoint firstPoint = trackPoints.get(0);
302                return firstPoint.getTime();
303
304        }
305
306        /**
307         * _more_
308         * 
309         * @param stormInfo
310         *            _more_
311         */
312        public void setStormInfo(StormInfo stormInfo) {
313                this.stormInfo = stormInfo;
314        }
315
316        /**
317         * _more_
318         * 
319         * @return _more_
320         */
321        public StormInfo getStormInfo() {
322                return stormInfo;
323        }
324
325        /**
326         * _more_
327         * 
328         * @param way
329         *            _more_
330         */
331        public void setWay(Way way) {
332                this.way = way;
333        }
334
335        /**
336         * _more_
337         * 
338         * @return _more_
339         */
340        public Way getWay() {
341                return way;
342        }
343
344        /**
345         * _more_
346         * 
347         * @param pts
348         *            _more_
349         */
350        public void setTrackPoints(List<StormTrackPoint> pts) {
351                this.trackPoints = new ArrayList<StormTrackPoint>(pts);
352        }
353
354        /**
355         * _more_
356         * 
357         * @return _more_
358         */
359        public List<StormTrackPoint> getTrackPoints() {
360                return trackPoints;
361        }
362
363        /**
364         * _more_
365         * 
366         * @return _more_
367         */
368        public List<DateTime> getTrackTimes() {
369                List<DateTime> trackTimes = new ArrayList();
370                for (StormTrackPoint stp : trackPoints) {
371                        trackTimes.add(stp.getTime());
372                }
373                return trackTimes;
374        }
375
376        /**
377         * _more_
378         * 
379         * @return _more_
380         */
381        public List<StormParam> getParams() {
382                if (params == null) {
383                        params = new ArrayList<StormParam>();
384                        Hashtable seenParam = new Hashtable();
385                        for (StormTrackPoint stp : trackPoints) {
386                                List<Real> reals = stp.getTrackAttributes();
387                                for (Real r : reals) {
388                                        RealType type = (RealType) r.getType();
389                                        if (seenParam.get(type) == null) {
390                                                seenParam.put(type, type);
391                                                params.add(new StormParam(type));
392                                        }
393                                }
394                        }
395                }
396
397                return params;
398        }
399
400        /**
401         * _more_
402         * 
403         * @return _more_
404         */
405        public List<EarthLocation> getLocations() {
406                List<EarthLocation> locs = new ArrayList();
407                for (StormTrackPoint stp : trackPoints) {
408                        locs.add(stp.getLocation());
409                }
410                return locs;
411        }
412
413        /**
414         * _more_
415         * 
416         * 
417         * 
418         * @param param
419         *            _more_
420         * @return _more_
421         * 
422         * @throws VisADException
423         *             _more_
424         */
425        public Real[] getTrackAttributeValues(StormParam param)
426                        throws VisADException {
427                if (param == null) {
428                        return null;
429                }
430                int size = trackPoints.size();
431                Real[] trackAttributes = new Real[size];
432                Real missing = null;
433                for (int i = 0; i < size; i++) {
434                        Real value = trackPoints.get(i).getAttribute(param);
435                        if (value == null) {
436                                if (i == 0) {
437                                        return null;
438                                }
439                                trackAttributes[i] = null;
440                        } else {
441                                if (missing == null) {
442                                        missing = value.cloneButValue(Double.NaN);
443                                }
444                                trackAttributes[i] = value;
445                        }
446                }
447                for (int i = 0; i < size; i++) {
448                        if (trackAttributes[i] == null) {
449                                trackAttributes[i] = missing;
450                        }
451                }
452                return trackAttributes;
453        }
454
455        /**
456         * _more_
457         * 
458         * @param trackAttributes
459         *            _more_
460         * @param i
461         *            _more_
462         * 
463         * @return _more_
464         */
465        public float findClosestAttr(float[] trackAttributes, int i) {
466                int up = i;
467                int down = i;
468                int size = trackAttributes.length;
469                float value = Float.NaN;
470                while (Float.isNaN(value)) {
471                        up++;
472                        down--;
473                        if ((up > 0) && (up < size)) {
474                                value = trackAttributes[up];
475                        }
476                        if ((down > 0) && (down < size)) {
477                                value = trackAttributes[down];
478                        }
479                }
480                return value;
481        }
482
483        /**
484         * _more_
485         * 
486         * @return _more_
487         */
488        public String toString() {
489                return trackId;
490        }
491
492        /**
493         * Return the index of the given track point. This kist finds the point with
494         * the same lat/lon
495         * 
496         * @param stp
497         *            The track point
498         * @return The index or -1 if not found
499         */
500        public int indexOf(StormTrackPoint stp) {
501                for (int i = 0; i < trackPoints.size(); i++) {
502                        if (trackPoints.get(i).getLocation().equals(stp.getLocation())) {
503                                return i;
504                        }
505                }
506                return -1;
507        }
508
509        /**
510         * _more_
511         * 
512         * @param o
513         *            _more_
514         * 
515         * @return _more_
516         */
517        public boolean equals(Object o) {
518                if (o == null) {
519                        return false;
520                }
521                if (!(o instanceof StormTrack)) {
522                        return false;
523                }
524                StormTrack other = (StormTrack) o;
525                return ((trackId.equals(other.trackId)));
526        }
527
528        public void putTemporaryProperty(Object key, Object value) {
529                temporaryProperties.put(key, value);
530        }
531
532        public Object getTemporaryProperty(Object key) {
533                return temporaryProperties.get(key);
534        }
535
536        static public StringBuffer toDiamond7(List<StormTrack> sts, String id)
537                        throws VisADException {
538                StringBuffer sb = new StringBuffer();
539                sb.append("diamond 7 " + id + "TropicalCycloneTrack" + "\n");
540                for (StormTrack st : sts) {
541                        st.toDiamond7(sb, id);
542                }
543                return sb;
544        }
545
546        public void toDiamond7(StringBuffer sb, String id) throws VisADException {
547                Calendar cal = Calendar.getInstance();
548                List<StormTrackPoint> tpoints = getTrackPoints();
549
550                sb.append("Name " + id + " " + way + " " + tpoints.size() + "\n");
551                for (StormTrackPoint stp : tpoints) {
552                        Date dttm = null;
553
554                        try {
555                                dttm = Util.makeDate(stp.getTime());
556                        } catch (Exception excp) {
557
558                        }
559                        cal.setTime(dttm);
560                        String year = Integer.toString(cal.get(Calendar.YEAR));
561                        int mm = cal.get(Calendar.MONTH);
562                        String mon = Integer.toString(mm);
563                        if (mm < 10)
564                                mon = "0" + mon;
565                        int dd = cal.get(Calendar.DAY_OF_MONTH);
566                        String day = Integer.toString(dd);
567                        if (dd < 10)
568                                day = "0" + day;
569                        int hour = cal.get(Calendar.HOUR_OF_DAY);
570                        int fhour = stp.getForecastHour();
571                        EarthLocation el = stp.getLocation();
572                        List<Real> attrs = stp.getTrackAttributes();
573
574                        sb.append(year.substring(2));
575                        sb.append(" ");
576                        sb.append(mon);
577                        sb.append(" ");
578                        sb.append(day);
579                        sb.append(" ");
580                        sb.append(hour);
581                        sb.append(" ");
582                        sb.append(fhour);
583                        sb.append(" ");
584                        sb.append(el.getLongitude().getValue(CommonUnit.degree));
585                        sb.append(" ");
586                        sb.append(el.getLatitude().getValue(CommonUnit.degree));
587                        sb.append(" ");
588
589                        // TODO: What to do with units?
590                        appendDiamondValue(sb, stp
591                                        .getAttribute(STIStormDataSource.PARAM_MAXWINDSPEED));
592                        appendDiamondValue(sb, stp
593                                        .getAttribute(STIStormDataSource.PARAM_MINPRESSURE));
594                        appendDiamondValue(sb, stp
595                                        .getAttribute(STIStormDataSource.PARAM_RADIUSMODERATEGALE));
596                        appendDiamondValue(sb, stp
597                                        .getAttribute(STIStormDataSource.PARAM_RADIUSWHOLEGALE));
598                        appendDiamondValue(sb, stp
599                                        .getAttribute(STIStormDataSource.PARAM_MOVESPEED));
600                        appendDiamondValue(sb, stp
601                                        .getAttribute(STIStormDataSource.PARAM_MOVEDIRECTION));
602
603                        sb.append("\n");
604                }
605        }
606
607        private void appendDiamondValue(StringBuffer sb, Real r) {
608                if (r == null || Double.isNaN(r.getValue()))
609                        sb.append(DIAMOND_MISSING_VALUE);
610                else
611                        sb.append(r.getValue());
612                sb.append(" ");
613        }
614
615}