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 }