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