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