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;
030
031 import java.io.BufferedReader;
032 import java.io.File;
033 import java.io.InputStreamReader;
034 import java.net.URI;
035 import java.net.URL;
036 import java.net.URLConnection;
037 import java.rmi.RemoteException;
038 import java.util.ArrayList;
039 import java.util.Hashtable;
040 import java.util.List;
041 import java.util.Vector;
042
043 import org.slf4j.Logger;
044 import org.slf4j.LoggerFactory;
045
046 import ucar.unidata.data.DataCategory;
047 import ucar.unidata.data.DataChoice;
048 import ucar.unidata.data.DataSelection;
049 import ucar.unidata.data.DataSelectionComponent;
050 import ucar.unidata.data.DataSourceDescriptor;
051 import ucar.unidata.data.DataSourceImpl;
052 import ucar.unidata.data.DirectDataChoice;
053 import ucar.unidata.idv.IntegratedDataViewer;
054 import ucar.unidata.util.StringUtil;
055
056 import visad.Data;
057 import visad.Text;
058 import visad.Tuple;
059 import visad.VisADException;
060 import visad.georef.LatLonTuple;
061
062 import edu.wisc.ssec.mcidas.adde.AddeTextReader;
063 import edu.wisc.ssec.mcidasv.chooser.PolarOrbitTrackChooser;
064 import edu.wisc.ssec.mcidasv.data.adde.sgp4.SGP4SatData;
065 import edu.wisc.ssec.mcidasv.data.adde.sgp4.SGP4unit;
066 import edu.wisc.ssec.mcidasv.data.adde.sgp4.SatelliteTleSGP4;
067 import edu.wisc.ssec.mcidasv.data.adde.sgp4.TLE;
068 import edu.wisc.ssec.mcidasv.data.adde.sgp4.Time;
069
070 /**
071 * Class for Two-Line-Element data sources, to plot orbit tracks
072 * on McIDAS-V display window.
073 *
074 * @author Tommy Jasmin
075 * @version $Revision$
076 */
077
078 public class PolarOrbitTrackDataSource extends DataSourceImpl {
079
080 private static final Logger logger = LoggerFactory.getLogger(PolarOrbitTrackDataSource.class);
081
082 /** list of twod categories */
083 private List twoDCategories;
084
085 private List tleCards = new ArrayList();
086 private List choices = new ArrayList();
087
088 private SGP4SatData data = new SGP4SatData();
089 private TLE tle;
090
091 public static double pi = SGP4unit.pi;
092
093 private Hashtable selectionProps;
094
095 /** time step between data points */
096 private int dTime = 1;
097
098 private SatelliteTleSGP4 prop = null;
099 private double julDate0 = 0.0;
100 private double julDate1 = 0.0;
101
102 /**
103 * Default bean constructor for persistence; does nothing.
104 */
105 public PolarOrbitTrackDataSource() {}
106
107 /**
108 * Create a new PolarOrbitTrackDataSource
109 *
110 * @param descriptor descriptor for this source
111 * @param filename ADDE URL
112 * @param properties extra properties for this source
113 *
114 */
115 public PolarOrbitTrackDataSource(DataSourceDescriptor descriptor,
116 String filename, Hashtable properties)
117 throws VisADException {
118 super(descriptor, filename, null, properties);
119 /*
120 System.out.println("\nPolarOrbitTrackDataSource:");
121 System.out.println(" descriptor=" + descriptor);
122 System.out.println(" filename=" + filename);
123 */
124 tleCards = new ArrayList();
125 choices = new ArrayList();
126
127 // we dealing with a local file?
128 if (properties.containsKey(PolarOrbitTrackChooser.LOCAL_FILE_KEY)) {
129 File f = (File) (properties.get(PolarOrbitTrackChooser.LOCAL_FILE_KEY));
130 logger.debug("Local file: " + f.getName());
131 URI uri = f.toURI();
132 properties.put(PolarOrbitTrackChooser.URL_NAME_KEY, uri.toString());
133 }
134
135 // not a file, must be URL or ADDE request
136 String key = PolarOrbitTrackChooser.TLE_SERVER_NAME_KEY;
137 if (properties.containsKey(key)) {
138 logger.debug("ADDE request...");
139 Object server = properties.get(key);
140 key = PolarOrbitTrackChooser.TLE_GROUP_NAME_KEY;
141 Object group = properties.get(key);
142 key = PolarOrbitTrackChooser.TLE_USER_ID_KEY;
143 Object user = properties.get(key);
144 key = PolarOrbitTrackChooser.TLE_PROJECT_NUMBER_KEY;
145 Object proj = properties.get(key);
146 key = PolarOrbitTrackChooser.DATASET_NAME_KEY;
147 Object descr = properties.get(key);
148 String url = "adde://" + server + "/textdata?&PORT=112&COMPRESS=gzip&USER=" + user + "&PROJ=" + proj + "&GROUP=" + group + "&DESCR=" + descr;
149 AddeTextReader reader = new AddeTextReader(url);
150 List lines = null;
151 if ("OK".equals(reader.getStatus())) {
152 lines = reader.getLinesOfText();
153 }
154 if (lines == null) {
155 notTLE();
156 return;
157 } else {
158 String[] cards = StringUtil.listToStringArray(lines);
159 for (int i=0; i<cards.length; i++) {
160 String str = cards[i];
161 if (str.length() > 0) {
162 tleCards.add(cards[i]);
163 int indx = cards[i].indexOf(" ");
164 if (indx < 0) {
165 choices.add(cards[i]);
166 }
167 }
168 }
169 }
170 } else {
171 try {
172 key = PolarOrbitTrackChooser.URL_NAME_KEY;
173 String urlStr = (String)(properties.get(key));
174 logger.debug("URL request: " + urlStr);
175 URL url = new URL(urlStr);
176 URLConnection urlCon = url.openConnection();
177 InputStreamReader isr = new InputStreamReader(urlCon.getInputStream());
178 BufferedReader tleReader = new BufferedReader(isr);
179 String nextLine = null;
180 while ((nextLine = tleReader.readLine()) != null) {
181 if (nextLine.length() > 0) {
182 tleCards.add(nextLine);
183 if (nextLine.length() < 50) {
184 choices.add(nextLine);
185 }
186 }
187 }
188 } catch (Exception e) {
189 notTLE();
190 return;
191 }
192 }
193 checkFirstEntry();
194 }
195
196 private void checkFirstEntry() {
197 if (tleCards.isEmpty()) {
198 notTLE();
199 return;
200 }
201 String card = (String)tleCards.get(1);
202 decodeCard1(card);
203 }
204
205 public void initAfterCreation() {
206 }
207
208 /**
209 * Make the data choices associated with this source.
210 */
211 protected void doMakeDataChoices() {
212 String category = "TLE";
213 for (int i=0; i<choices.size(); i++) {
214 String name = ((String)choices.get(i)).trim();
215 addDataChoice(
216 new DirectDataChoice(
217 this, name, name, name,
218 DataCategory.parseCategories(category, false)));
219 }
220 }
221
222 /**
223 * Initialize the {@link ucar.unidata.data.DataCategory} objects that
224 * this data source uses.
225 */
226 private void makeCategories() {
227 twoDCategories = DataCategory.parseCategories("TLE", false);
228 }
229
230 /**
231 * Actually get the data identified by the given DataChoce. The default is
232 * to call the getDataInner that does not take the requestProperties. This
233 * allows other, non unidata.data DataSource-s (that follow the old API)
234 * to work.
235 *
236 * @param dataChoice The data choice that identifies the requested
237 * data.
238 * @param category The data category of the request.
239 * @param dataSelection Identifies any subsetting of the data.
240 * @param requestProperties Hashtable that holds any detailed request
241 * properties.
242 *
243 * @return The visad.Text object
244 *
245 * @throws RemoteException Java RMI problem
246 * @throws VisADException VisAD problem
247 */
248
249 protected Data getDataInner(DataChoice dataChoice, DataCategory category,
250 DataSelection dataSelection,
251 Hashtable requestProperties)
252 throws VisADException, RemoteException {
253 /*
254 System.out.println("\ngetDataInner:");
255 System.out.println(" dTime=" + dTime);
256 System.out.println(" dataChoice=" + dataChoice);
257 System.out.println(" category=" + category);
258 System.out.println(" dataSelection=" + dataSelection + "\n");
259 System.out.println("categories for dataChoice: " + dataChoice.getCategories());
260 */
261
262 boolean gotit = false;
263 int index = -1;
264 String choiceName = dataChoice.getName();
265 String tleLine1 = "";
266 String tleLine2 = "";
267
268 while(!gotit) {
269 index++;
270 String name = ((String)tleCards.get(index)).trim();
271 if (name.equals(choiceName)) {
272 data.name = name;
273 /*
274 System.out.println("\n" + tleCards.get(index));
275 System.out.println(tleCards.get(index+1));
276 System.out.println(tleCards.get(index+2) + "\n");
277 */
278 index++;
279 String card = (String)tleCards.get(index);
280 tleLine1 = card;
281 int ncomps = decodeCard1(card);
282 if (ncomps < 0) return null;
283 index++;
284 card = (String)tleCards.get(index);
285 tleLine2 = card;
286 ncomps += decodeCard2(card);
287 gotit= true;
288 }
289 if (index+3 > tleCards.size()) gotit = true;
290 }
291 if (gotit == false) return null;
292
293 this.selectionProps = dataSelection.getProperties();
294 /*
295 Enumeration propEnum = this.selectionProps.keys();
296 for (int i = 0; propEnum.hasMoreElements(); i++) {
297 String key = propEnum.nextElement().toString();
298 String val = (String)this.selectionProps.get(key);
299 System.out.println("key=" + key + " val=" + val);
300 }
301 */
302 tle = new TLE(choiceName, tleLine1, tleLine2);
303
304 String endStr = (String)this.selectionProps.get("ETime");
305 Double dEnd = new Double(endStr);
306 double endJulianDate = dEnd.doubleValue();
307 julDate1 = endJulianDate;
308
309 try
310 {
311 prop = new SatelliteTleSGP4(tle.getSatName(), tle.getLine1(), tle.getLine2());
312 prop.setShowGroundTrack(false);
313 }
314 catch(Exception e)
315 {
316 logger.error("Error Creating SGP4 Satellite e=" + e);
317 System.exit(1);
318 }
319
320 Time time = new Time(
321 (new Integer((String)this.selectionProps.get("Year"))).intValue(),
322 (new Integer((String)this.selectionProps.get("Month"))).intValue(),
323 (new Integer((String)this.selectionProps.get("Day"))).intValue(),
324 (new Integer((String)this.selectionProps.get("Hours"))).intValue(),
325 (new Integer((String)this.selectionProps.get("Mins"))).intValue(),
326 (new Double((String)this.selectionProps.get("Secs"))).doubleValue());
327 double julianDate = time.getJulianDate();
328 julDate0 = julianDate;
329 Vector v = new Vector();
330
331 while (julianDate <= julDate1) {
332 // prop to the desired time
333 prop.propogate2JulDate(julianDate);
334
335 // get the lat/long/altitude [radians, radians, meters]
336 double[] lla = prop.getLLA();
337 double lat = lla[0]*180.0/Math.PI;
338 double lon = lla[1]*180.0/Math.PI;
339
340 /*
341 System.out.println(time.getDateTimeStr() + " Lat: " + lat
342 + " Lon: " + lon
343 + " Alt: " + alt);
344 */
345 Tuple data = new Tuple(new Data[] { new Text(time.getDateTimeStr()),
346 new LatLonTuple(
347 lat,
348 lon
349 )}
350 );
351 v.add(data);
352 time.add(Time.MINUTE, dTime);
353 julianDate = time.getJulianDate();
354 }
355
356 return new Tuple((Data[]) v.toArray(new Data[v.size()]), false);
357 }
358
359 public double getNearestAltToGroundStation(double gsLat, double gsLon) {
360 double retAlt = 0.0;
361 Time time = new Time(
362 (new Integer((String)this.selectionProps.get("Year"))).intValue(),
363 (new Integer((String)this.selectionProps.get("Month"))).intValue(),
364 (new Integer((String)this.selectionProps.get("Day"))).intValue(),
365 (new Integer((String)this.selectionProps.get("Hours"))).intValue(),
366 (new Integer((String)this.selectionProps.get("Mins"))).intValue(),
367 (new Double((String)this.selectionProps.get("Secs"))).doubleValue());
368
369 double minDist = 999999.99;
370 double julianDate = julDate0;
371
372 while (julianDate <= julDate1) {
373 // prop to the desired time
374 prop.propogate2JulDate(julianDate);
375
376 // get the lat/long/altitude [radians, radians, meters]
377 double[] lla = prop.getLLA();
378 double lat = lla[0]*180.0/Math.PI;
379 double lon = lla[1]*180.0/Math.PI;
380 double alt = lla[2];
381 //System.out.println(" " + time.getDateTimeStr() + ": lat=" + lat + " lon=" + lon + " alt=" + alt);
382
383 double latDiff = (gsLat - lat) * (gsLat - lat);
384 double lonDiff = (gsLon - lon) * (gsLon - lon);
385 double dist = Math.sqrt(latDiff+lonDiff);
386 if (dist < minDist) {
387 minDist = dist;
388 retAlt = alt;
389 }
390 time.add(Time.MINUTE, dTime);
391 julianDate = time.getJulianDate();
392 }
393
394 return retAlt;
395 }
396
397 private int decodeCard1(String card) {
398 /*
399 System.out.println("\ndecodeCard1:");
400 System.out.println(" card=" + card);
401 System.out.println(" length=" + card.length());
402 */
403 int satId = 0;
404 double ddd = 1.0;
405 double firstDev = 1.0;
406 int ephemerisType = 0;
407 int elementNumber = 0;
408
409 int ret = 0;
410 if (card.length() < 69) {
411 notTLE();
412 return -1;
413 }
414 int ck1 = checksum(card.substring(0, 68));
415 String str = card.substring(0, 1);
416 if (str.equals("1")) {
417 satId = getInt(2, 7, card);
418 //System.out.println(" satId = " + satId);
419 data.satnum = satId;
420 ++ret;
421
422 data.classification = card.substring(7, 8);
423 data.intldesg = card.substring(9, 17);
424 int yy = getInt(18, 20, card);
425 data.epochyr = yy;
426 ++ret;
427
428 ddd = getDouble(20, 32, card);
429 //System.out.println(" ddd = " + ddd);
430 data.epochdays = ddd;
431 ++ret;
432
433 firstDev = getDouble(33, 43, card);
434 //System.out.println(" firstDev = " + firstDev);
435 data.ndot = firstDev;
436 ++ret;
437
438 if((card.substring(44, 52)).equals(" "))
439 {
440 data.nddot = 0;
441 data.nexp = 0;
442 }
443 else
444 {
445 data.nddot = getDouble(44, 50, card) / 1.0E5;
446 data.nexp = getInt(50, 52, card);
447 }
448 //System.out.println(" nddot=" + data.nddot);
449 //System.out.println(" nexp=" + data.nexp);
450
451 data.bstar = getDouble(53, 59, card) / 1.0E5;
452 data.ibexp = getInt(59, 61, card);
453 //System.out.println(" bstar=" + data.bstar);
454 //System.out.println(" ibexp=" + data.ibexp);
455
456 try {
457 ephemerisType = getInt(62, 63, card);
458 //System.out.println(" ephemerisType = " + ephemerisType);
459 data.numb = ephemerisType;
460 ++ret;
461
462 elementNumber = getInt(64, 68, card);
463 //System.out.println(" elementNumber = " + elementNumber);
464 data.elnum = elementNumber;
465 ++ret;
466 } catch (Exception e) {
467 logger.error("Warning: Error Reading numb or elnum from TLE line 1 sat#:" + data.satnum);
468 }
469
470 int check = card.codePointAt(68) - 48;
471 if (check != ck1) {
472 notTLE();
473 // logger.error("***** Failed checksum *****");
474 ret = -1;
475 }
476 }
477 return ret;
478 }
479
480 private int decodeCard2(String card) {
481 /*
482 System.out.println("\ndecodeCard2:");
483 System.out.println(" card=" + card);
484 System.out.println(" length=" + card.length());
485 */
486 double inclination = 1.0;
487 double rightAscension = 1.0;
488 double eccentricity = 1.0;
489 double argOfPerigee = 1.0;
490 double meanAnomaly = 1.0;
491 double meanMotion = 1.0;
492 int revolutionNumber = 0;
493
494 int ret = 0;
495 //System.out.println("\n" + card);
496 if (card.length() < 69) {
497 notTLE();
498 return -1;
499 }
500 int ck1 = checksum(card.substring(0, 68));
501 String str = card.substring(0, 1);
502 if (str.equals("2")) {
503 int nsat = getInt(2, 7, card);
504 //System.out.println(" nsat = " + nsat + " data.satnum=" + data.satnum);
505 if (nsat != data.satnum) {
506 logger.error("Warning TLE line 2 Sat Num doesn't match line1 for sat: " + data.name);
507 } else {
508 inclination = getDouble(8, 16, card);
509 data.inclo = inclination;
510 //System.out.println(" inclo = " + data.inclo);
511 ++ret;
512
513 rightAscension = getDouble(17, 25, card);
514 data.nodeo = rightAscension;
515 //System.out.println(" nodeo = " + data.nodeo);
516 ++ret;
517
518 eccentricity = getDouble(26, 33, card) / 1.0E7;
519 data.ecco = eccentricity;
520 //System.out.println(" ecco = " + data.ecco);
521 ++ret;
522
523 argOfPerigee = getDouble(34, 42, card);
524 data.argpo = argOfPerigee;
525 //System.out.println(" argpo = " + data.argpo);
526 ++ret;
527
528 meanAnomaly = getDouble(43, 51, card);
529 data.mo = meanAnomaly;
530 //System.out.println(" mo = " + data.mo);
531 ++ret;
532
533 meanMotion = getDouble(52, 63, card);
534 data.no = meanMotion;
535 //System.out.println(" no = " + data.no);
536 ++ret;
537
538 try {
539 revolutionNumber = getInt(63, 68, card);
540 data.revnum = revolutionNumber;
541 //System.out.println(" revnum = " + data.revnum);
542 ++ret;
543 } catch (Exception e) {
544 logger.error("Warning: Error Reading revnum from TLE line 2 sat#:" + data.satnum + "\n" + e.toString());
545 data.revnum = -1;
546 }
547
548 int check = card.codePointAt(68) - 48;
549 if (check != ck1) {
550 notTLE();
551 // logger.error("***** Failed checksum *****");
552 ret = -1;
553 }
554 }
555 }
556 return ret;
557 }
558
559 private int getInt(int beg, int end, String card) {
560 String str = card.substring(beg, end);
561 str = str.trim();
562 return (new Integer(str)).intValue();
563 }
564
565 private double getDouble(int beg, int end, String card) {
566 String str = card.substring(beg, end);
567 str = str.trim();
568 return (new Double(str)).doubleValue();
569 }
570
571 private int checksum(String str) {
572 int sum = 0;
573 byte[] bites = str.getBytes();
574 for (int i=0; i<bites.length; i++) {
575 int val = (int)bites[i];
576 if ((val > 47) && (val < 58)) {
577 sum += val - 48;
578 } else if (val == 45) {
579 ++sum;
580 }
581 }
582 return sum % 10;
583 }
584
585 protected void initDataSelectionComponents(
586 List<DataSelectionComponent> components, final DataChoice dataChoice) {
587 /*
588 System.out.println("\ninitDataSelectionComponents:");
589 System.out.println(" components=" + components);
590 System.out.println(" dataChoice=" + dataChoice);
591 System.out.println(" categories=" + dataChoice.getCategories());
592 System.out.println(" displayCategory=" + dataChoice.getDisplayCategory());
593 */
594 clearTimes();
595 IntegratedDataViewer idv = getDataContext().getIdv();
596 idv.showWaitCursor();
597 try {
598 TimeRangeSelection timeSelection = new TimeRangeSelection(this);
599 components.add(timeSelection);
600 } catch (Exception e) {
601 logger.error("problem creating TimeRangeSelection e=" + e);
602 }
603 idv.showNormalCursor();
604 }
605
606 /**
607 * Show the dialog
608 *
609 * @param initTabName What tab should we show. May be null.
610 * @param modal Is dialog modal
611 *
612 * @return success
613 */
614 public boolean showPropertiesDialog(String initTabName, boolean modal) {
615 //System.out.println("\n\nshowPropertiesDialog:");
616 boolean ret = super.showPropertiesDialog(initTabName, modal);
617 return ret;
618 }
619
620 public int getDTime() {
621 return dTime;
622 }
623
624 public void setDTime(int val) {
625 //System.out.println("PolarOrbitTrackDataSource setDTime: val=" + val);
626 dTime = val;
627 }
628
629 private void notTLE() {
630 tleCards = new ArrayList();
631 choices = new ArrayList();
632 setInError(true, "\nSource does not contain TLE data");
633 }
634 }
635