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