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 }