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