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