Esempio n. 1
0
class Picker(QWidget):
    date_format = "%Y-%m-%d %HZ"
    run_format = "%d %B %Y / %H%M UTC"

    async_obj = AsyncThreads(2, debug)

    def __init__(self, config, **kwargs):
        """
        Construct the main picker widget: a means for interactively selecting
        which sounding profile(s) to view.
        """

        super(Picker, self).__init__(**kwargs)
        self.data_sources = data_source.loadDataSources()
        self.config = config
        self.skew = None

        # default the sounding location to OUN because obviously I'm biased
        self.loc = None
        # the index of the item in the list that corresponds
        # to the profile selected from the list
        self.prof_idx = []
        # set the default profile type to Observed
        self.model = "Observed"
        # this is the default model initialization time
        self.all_times = sorted(
            self.data_sources[self.model].getAvailableTimes())
        self.run = [t for t in self.all_times if t.hour in [0, 12]][-1]

        urls = data_source.pingURLs(self.data_sources)
        self.has_connection = any(urls.values())
        self.strictQC = True

        # initialize the UI
        self.__initUI()

    def __initUI(self):
        """
        Initialize the main user interface.
        """

        # Give the main window a layout. Using GridLayout
        # in order to control placement of objects.

        self.layout = QGridLayout()
        self.setLayout(self.layout)

        self.view = self.create_map_view()
        self.view.hasInternet(self.has_connection)

        self.button = QPushButton('Generate Profiles')
        self.button.clicked.connect(self.complete_name)
        self.button.setDisabled(True)

        self.select_flag = False
        self.all_profs = QPushButton("Select All")
        self.all_profs.clicked.connect(self.select_all)
        self.all_profs.setDisabled(True)

        self.save_view_button = QPushButton('Save Map View as Default')
        self.save_view_button.clicked.connect(self.save_view)

        self.profile_list = QListWidget()
        self.profile_list.setSelectionMode(QAbstractItemView.MultiSelection)
        self.profile_list.setDisabled(True)

        # create subwidgets that will hold the individual GUI items
        self.left_data_frame = QWidget()
        self.right_map_frame = QWidget()
        # set the layouts for these widgets
        self.left_layout = QVBoxLayout()
        self.right_layout = QGridLayout()  # QVBoxLayout()
        self.left_data_frame.setLayout(self.left_layout)

        self.right_map_frame.setLayout(self.right_layout)
        #print(self.run)
        self.cal = Calendar(self, dt_avail=self.run)
        self.cal.setSelectedDate(self.run)
        self.cal.clicked.connect(self.update_from_cal)
        self.cal_date = self.cal.selectedDate()
        filt_times = [
            t for t in self.all_times
            if t.day == self.cal_date.day() and t.year == self.cal_date.year()
            and t.month == self.cal_date.month()
        ]

        # create dropdown menus
        models = sorted(self.data_sources.keys())
        self.model_dropdown = self.dropdown_menu(models)
        self.model_dropdown.setCurrentIndex(models.index(self.model))

        # Setup the map
        projs = [('npstere', 'Northern Hemisphere'), ('merc', 'Tropics'),
                 ('spstere', 'Southern Hemisphere')]
        if ('map', 'proj') in self.config:
            proj = self.config['map', 'proj']
            proj_idx = list(zip(*projs))[0].index(proj)
        else:
            proj_idx = 0
        self.map_dropdown = self.dropdown_menu(list(zip(*projs))[1])
        self.map_dropdown.setCurrentIndex(proj_idx)

        # Set up the run dropdown box and select the correct index
        self.run_dropdown = self.dropdown_menu(
            [t.strftime(Picker.run_format) for t in filt_times])
        try:
            self.run_dropdown.setCurrentIndex(filt_times.index(self.run))
        except ValueError as e:
            logging.error("Run dropdown is missing its times ... ?")
            logging.exception(e)
        # connect the click actions to functions that do stuff
        self.model_dropdown.activated.connect(self.get_model)
        self.map_dropdown.activated.connect(self.get_map)
        self.run_dropdown.activated.connect(self.get_run)

        # Create text labels to describe the various menus
        self.type_label = QLabel("Select Sounding Source")
        self.date_label = QLabel("Select Forecast Time")
        self.map_label = QLabel("Select Map Area")
        self.run_label = QLabel("Select Cycle")
        self.date_label.setDisabled(True)

        # add the elements to the left side of the GUI
        self.left_layout.addWidget(self.type_label)
        self.left_layout.addWidget(self.model_dropdown)
        self.left_layout.addWidget(self.run_label)
        self.left_layout.addWidget(self.cal)
        self.left_layout.addWidget(self.run_dropdown)
        self.left_layout.addWidget(self.date_label)
        self.left_layout.addWidget(self.profile_list)
        self.left_layout.addWidget(self.all_profs)
        self.left_layout.addWidget(self.button)

        # add the elements to the right side of the GUI
        self.right_layout.setColumnMinimumWidth(0, 500)
        self.right_layout.addWidget(self.map_label, 0, 0, 1, 1)
        self.right_layout.addWidget(self.save_view_button, 0, 1, 1, 1)
        self.right_layout.addWidget(self.map_dropdown, 1, 0, 1, 2)
        self.right_layout.addWidget(self.view, 2, 0, 1, 2)

        # add the left and right sides to the main window
        self.layout.addWidget(self.left_data_frame, 0, 0, 1, 1)
        self.layout.addWidget(self.right_map_frame, 0, 1, 1, 1)
        self.left_data_frame.setMaximumWidth(280)

    def create_map_view(self):
        """
        Create a clickable map that will be displayed in the GUI.
        Will eventually be re-written to be more general.

        Returns
        -------
        view : QWebView object
        """

        # minimumWidth=800, minimumHeight=500,
        view = MapWidget(self.data_sources[self.model],
                         self.run,
                         self.async_obj,
                         cfg=self.config)
        view.clicked.connect(self.map_link)

        return view

    def dropdown_menu(self, item_list):
        """
        Create and return a dropdown menu containing items in item_list.

        Params
        ------
        item_list : a list of strings for the contents of the dropdown menu

        Returns
        -------
        dropdown : a QtGui.QComboBox object
        """
        logging.debug("Calling full_gui.dropdown_menu")
        # create the dropdown menu
        dropdown = QComboBox()
        # set the text as editable so that it can have centered text
        dropdown.setEditable(True)
        dropdown.lineEdit().setReadOnly(True)
        dropdown.lineEdit().setAlignment(Qt.AlignCenter)

        # add each item in the list to the dropdown
        for item in item_list:
            dropdown.addItem(item)

        return dropdown

    def update_from_cal(self, dt, updated_model=False):
        """
        Update the dropdown list and the forecast times list if a new date
        is selected in the calendar app.
        """

        self.update_run_dropdown(updated_model=updated_model)

        self.view.setDataSource(self.data_sources[self.model], self.run)
        self.update_list()

    def update_list(self):
        """
        Update the list with new forecast times.

        :param list:
        :return:
        """
        logging.debug("Calling full_gui.update_list")
        if self.select_flag:
            self.select_all()
        self.profile_list.clear()
        self.prof_idx = []
        timelist = []

        # If the run is outside the available times.
        if self.run == date.datetime(1700, 1, 1, 0, 0, 0):
            self.profile_list.setDisabled(True)
            self.all_profs.setDisabled(True)
            self.date_label.setDisabled(True)
        else:
            fcst_hours = self.data_sources[self.model].getForecastHours()
            if fcst_hours != [0]:
                self.profile_list.setEnabled(True)
                self.all_profs.setEnabled(True)
                self.date_label.setEnabled(True)
                for fh in fcst_hours:
                    fcst_str = (self.run + date.timedelta(hours=fh)).strftime(
                        Picker.date_format) + "   (F%03d)" % fh
                    timelist.append(fcst_str)
            else:
                self.profile_list.setDisabled(True)
                self.all_profs.setDisabled(True)
                self.date_label.setDisabled(True)

        # Loop throught the timelist and each string to the list
        for item in timelist:
            self.profile_list.addItem(item)

        self.profile_list.update()
        self.all_profs.setText("Select All")
        self.select_flag = False

    def update_datasource_dropdown(self, selected="Observed"):
        """
        Updates the dropdown menu that contains the available
        data sources
        :return:
        """
        logging.debug("Calling full_gui.update_datasource_dropdown")

        for i in range(self.model_dropdown.count()):
            self.model_dropdown.removeItem(0)

        self.data_sources = data_source.loadDataSources()
        models = sorted(self.data_sources.keys())
        for model in models:
            self.model_dropdown.addItem(model)

        self.model_dropdown.setCurrentIndex(models.index(selected))
        self.get_model(models.index(selected))

    def update_run_dropdown(self, updated_model=False):
        """
        Updates the dropdown menu that contains the model run
        information.
        :return:
        """
        logging.debug("Calling full_gui.update_run_dropdown")

        if self.model.startswith("Local"):
            url = self.data_sources[self.model].getURLList(
                outlet="Local")[0].replace("file://", "")

            def getTimes():
                return self.data_sources[self.model].getAvailableTimes(url)
        else:

            def getTimes():
                return self.data_sources[self.model].getAvailableTimes(
                    dt=self.cal_date)

        self.cal_date = self.cal.selectedDate()

        # Function to update the times.
        def update(times):
            self.run_dropdown.clear(
            )  # Clear all of the items from the dropdown
            times = times[0]
            time_span = self.data_sources[self.model].updateTimeSpan()
            for outlet in time_span:
                if np.asarray(outlet).all() == None:
                    span = True
                else:
                    dt_earliest = outlet[0]
                    dt_avail = outlet[1]
                    span = False
            if span is True and len(times) > 0:
                dt_avail = max(times)
                dt_earliest = min(times)
            self.cal.setLatestAvailable(dt_avail)
            self.cal.setEarliestAvailable(dt_earliest)
            self.cal_date = self.cal.selectedDate()
            self.cal.update()

            # Filter out only times for the specified date.
            filtered_times = []
            for i, data_time in enumerate(times):
                if data_time.day == self.cal_date.day(
                ) and data_time.year == self.cal_date.year(
                ) and data_time.month == self.cal_date.month():
                    self.run_dropdown.addItem(
                        data_time.strftime(Picker.run_format))
                    filtered_times.append(i)

            if len(filtered_times) > 0:
                filtered_times = np.sort(np.asarray(filtered_times))
                times = times[filtered_times.min():filtered_times.max() + 1]
                # Pick the index for which to highlight
                if self.model == "Observed":
                    try:
                        # Try to grab the 0 or 12 UTC data for this day (or 3 or 15 if before 5/1/1957)
                        if self.cal_date.toPython() >= date.datetime(
                                1957, 5, 1).date():
                            synoptic_times = [0, 12]
                        else:
                            synoptic_times = [3, 15]
                        self.run = [
                            t for t in times
                            if t.hour in synoptic_times and t.day ==
                            self.cal_date.day() and t.month == self.cal_date.
                            month() and t.year == self.cal_date.year()
                        ][-1]
                    except Exception as e:
                        logging.exception(e)
                        self.run = times[-1]
                else:
                    self.run = times[-1]
            else:
                self.run = date.datetime(1700, 1, 1, 0, 0, 0)
            self.run_dropdown.update()
            if len(filtered_times) > 0:
                self.run_dropdown.setEnabled(True)
                self.run_dropdown.setCurrentIndex(times.index(self.run))
            elif len(filtered_times) == 0:
                if self.model == "Observed":
                    string = "obs"
                else:
                    string = "runs"
                self.run_dropdown.addItem(
                    self.tr("- No " + string + " available - "))
                self.run_dropdown.setCurrentIndex(0)
                self.run_dropdown.update()
                self.run_dropdown.setEnabled(False)

        # Post the getTimes to update.  This will re-write the list of times in the dropdown box that
        # match the date selected in the calendar.
        async_id = self.async_obj.post(getTimes, update)
        self.async_obj.join(async_id)

    def map_link(self, point):
        """
        Change the text of the button based on the user click.
        """
        logging.debug("Calling full_gui.map_link")
        if point is None:
            self.loc = None
            self.disp_name = None
            self.button.setText('Generate Profiles')
            self.button.setDisabled(True)
        elif self.model == "Local WRF-ARW":
            self.loc = point
            self.disp_name = "User Selected"
            self.button.setText(self.disp_name + ' | Generate Profiles')
            self.button.setEnabled(True)
            self.areal_lon, self.areal_y = point

        else:
            self.loc = point  # url.toString().split('/')[-1]
            if point['icao'] != "":
                self.disp_name = point['icao']
            elif point['iata'] != "":
                self.disp_name = point['iata']
            else:
                self.disp_name = point['srcid'].upper()

            self.button.setText(self.disp_name + ' | Generate Profiles')
            if self.has_connection:
                self.button.setEnabled(True)

    @crasher(exit=False)
    def complete_name(self):
        """
        Handles what happens when the user clicks a point on the map
        """
        logging.debug("Calling full_gui.complete_name")
        if self.loc is None:
            return
        else:
            self.prof_idx = []
            selected = self.profile_list.selectedItems()
            for item in selected:
                idx = self.profile_list.indexFromItem(item).row()
                if idx in self.prof_idx:
                    continue
                else:
                    self.prof_idx.append(idx)

            fcst_hours = self.data_sources[self.model].getForecastHours()

            if fcst_hours != [0] and len(self.prof_idx) > 0 or fcst_hours == [
                    0
            ]:
                self.prof_idx.sort()
                n_tries = 0
                while True:
                    try:
                        self.skewApp(ntry=n_tries)
                    except data_source.DataSourceError as e1:
                        # We've run out of data sources. Uh-oh.
                        logging.exception(e1)
                        if self.skew is not None:
                            self.skew.closeIfEmpty()
                        raise IOError(
                            "No outlet found with the requested profile!")
                    except Exception as e:
                        if debug:
                            print(traceback.format_exc())
                        n_tries += 1
                        logging.exception(e)
                    else:
                        break

    def get_model(self, index):
        """
        Get the user's model selection
        """
        logging.debug("Calling full_gui.get_model")
        self.model = self.model_dropdown.currentText()

        self.update_from_cal(None, updated_model=True)

    def get_run(self, index):
        """
        Get the user's run hour selection for the model
        """
        logging.debug("Calling full_gui.get_run")
        self.run = date.datetime.strptime(self.run_dropdown.currentText(),
                                          Picker.run_format)
        self.view.setCurrentTime(self.run)
        self.update_list()

    def get_map(self):
        """
        Get the user's map selection
        """
        logging.debug("Calling full_gui.get_map")
        proj = {
            'Northern Hemisphere': 'npstere',
            'Tropics': 'merc',
            'Southern Hemisphere': 'spstere'
        }[self.map_dropdown.currentText()]
        self.view.setProjection(proj)

    def save_view(self):
        """
        Save the map projection to the config file
        """
        self.view.saveProjection(self.config)

    def select_all(self):
        logging.debug("Calling full_gui.select_all")
        items = self.profile_list.count()
        if not self.select_flag:
            for i in range(items):
                if self.profile_list.item(i).text() in self.prof_idx:
                    continue
                else:
                    self.profile_list.item(i).setSelected(True)
            self.all_profs.setText("Deselect All")
            self.select_flag = True
        else:
            for i in range(items):
                self.profile_list.item(i).setSelected(False)
            self.all_profs.setText("Select All")
            self.select_flag = False

    def skewApp(self, filename=None, ntry=0):
        logging.debug("Calling full_gui.skewApp")
        """
        Create the SPC style SkewT window, complete with insets
        and magical funtimes.
        :return:
        """

        logging.debug("Calling full_gui.skewApp")

        failure = False

        exc = ""

        # if the profile is an archived file, load the file from
        # the hard disk
        if filename is not None:
            logging.info("Trying to load file from local disk...")

            model = "Archive"
            prof_collection, stn_id = self.loadArchive(filename)
            logging.info(
                "Successfully loaded the profile collection for this file...")
            disp_name = stn_id
            observed = True
            fhours = None

            # Determine if the dataset passed was from a model or is observed
            if len(prof_collection._dates) > 1:
                prof_idx = self.prof_idx
                fhours = [
                    "F%03d" % fh for idx, fh in enumerate(self.data_sources[
                        self.model].getForecastHours()) if idx in prof_idx
                ]
                observed = False
            else:
                fhours = None
                observed = True

            run = prof_collection.getCurrentDate()

        else:
            # otherwise, download with the data thread
            logging.info("Loading a real-time data stream...")
            prof_idx = self.prof_idx
            disp_name = self.disp_name
            run = self.run
            model = self.model
            observed = self.data_sources[model].isObserved()

            if self.data_sources[model].getForecastHours() == [0]:
                prof_idx = [0]

            logging.info("Program is going to load the data...")
            ret = loadData(self.data_sources[model],
                           self.loc,
                           run,
                           prof_idx,
                           ntry=ntry)

            # failure variable makes sure the data actually exists online.
            if isinstance(ret[0], Exception):
                exc = ret[0]
                failure = True
                logging.info(
                    "There was a problem with loadData() in obtaining the data from the Internet."
                )
            else:
                logging.info("Data was found and successfully decoded!")
                prof_collection = ret[0]

            fhours = [
                "F%03d" % fh for idx, fh in enumerate(self.data_sources[
                    self.model].getForecastHours()) if idx in prof_idx
            ]

        # If the observed or model profile (not Archive) successfully loaded)
        if not failure:
            prof_collection.setMeta('model', model)
            prof_collection.setMeta('run', run)
            prof_collection.setMeta('loc', disp_name)
            prof_collection.setMeta('fhour', fhours)
            prof_collection.setMeta('observed', observed)

            if not prof_collection.getMeta('observed'):
                # If it's not an observed profile, then generate profile objects in background.
                prof_collection.setAsync(Picker.async_obj)

            if self.skew is None:
                logging.debug("Constructing SPCWindow")
                # If the SPCWindow isn't shown, set it up.
                self.skew = SPCWindow(parent=self.parent(), cfg=self.config)
                self.parent().config_changed.connect(
                    self.skew.centralWidget().updateConfig)
                self.skew.closed.connect(self.skewAppClosed)
                self.skew.show()

            logging.debug("Focusing on the SkewApp")
            self.focusSkewApp()
            logging.debug("Adding the profile collection to SPCWindow")
            self.skew.addProfileCollection(prof_collection,
                                           check_integrity=self.strictQC)
        else:
            print("There was an exception:", exc)

            raise exc

    def skewAppClosed(self):
        """
        Handles the user closing the SPC window.
        """
        self.skew = None

    def focusSkewApp(self):
        if self.skew is not None:
            self.skew.activateWindow()
            self.skew.setFocus()
            self.skew.raise_()

    def keyPressEvent(self, e):
        if e.key() == 61 or e.key() == 45:
            self.view.keyPressEvent(e)

    def loadArchive(self, filename):
        """
        Get the archive sounding based on the user's selections.
        Also reads it using the Decoders and gets both the stationID and the profile objects
        for that archive sounding.  Tries a variety of decoders available to the program.
        """
        logging.debug(
            "Looping over all decoders to find which one to use to decode User Selected file."
        )
        for decname, deccls in getDecoders().items():
            try:
                dec = deccls(filename)
                break
            except Exception as e:
                logging.exception(e)
                dec = None
                continue

        if dec is None:
            raise IOError("Could not figure out the format of '%s'!" %
                          filename)
        # Returns the set of profiles from the file that are from the "Profile" class.
        logging.debug('Get the profiles from the decoded file.')
        profs = dec.getProfiles()
        stn_id = dec.getStnId()

        return profs, stn_id

    def hasConnection(self):
        return self.has_connection

    def setStrictQC(self, val):
        self.strictQC = val
Esempio n. 2
0
class Picker(QWidget):
    date_format = "%Y-%m-%d %HZ"
    run_format = "%d %B %Y / %H%M UTC"

    async = AsyncThreads(2, debug)

    def __init__(self, config, **kwargs):
        """
        Construct the main picker widget: a means for interactively selecting
        which sounding profile(s) to view.
        """
        
        super(Picker, self).__init__(**kwargs)
        self.data_sources = data_source.loadDataSources()
        self.config = config
        self.skew = None

        ## default the sounding location to OUN because obviously I'm biased
        self.loc = None
        ## the index of the item in the list that corresponds
        ## to the profile selected from the list
        self.prof_idx = []
        ## set the default profile type to Observed
        self.model = "Observed"
        ## this is the default model initialization time
        self.run = [ t for t in self.data_sources[self.model].getAvailableTimes() if t.hour in [0, 12] ][-1]

        urls = data_source.pingURLs(self.data_sources)
        self.has_connection = any( urls.values() )

        ## initialize the UI
        self.__initUI()

    def __initUI(self):
        """
        Initialize the main user interface.
        """

        ## Give the main window a layout. Using GridLayout
        ## in order to control placement of objects.

        self.layout = QGridLayout()
        self.setLayout(self.layout)

        self.view = self.create_map_view()
        self.view.hasInternet(self.has_connection)

        self.button = QPushButton('Generate Profiles')
        self.button.clicked.connect(self.complete_name)
        self.button.setDisabled(True)

        self.select_flag = False
        self.all_profs = QPushButton("Select All")
        self.all_profs.clicked.connect(self.select_all)
        self.all_profs.setDisabled(True)

        self.save_view_button = QPushButton('Save Map View as Default')
        self.save_view_button.clicked.connect(self.save_view)

        self.profile_list = QListWidget()
        self.profile_list.setSelectionMode(QAbstractItemView.MultiSelection)
        self.profile_list.setDisabled(True)

        ## create subwidgets that will hold the individual GUI items
        self.left_data_frame = QWidget()
        self.right_map_frame = QWidget()
        ## set the layouts for these widgets
        self.left_layout = QVBoxLayout()
        self.right_layout = QGridLayout() #QVBoxLayout()
        self.left_data_frame.setLayout(self.left_layout)
        self.right_map_frame.setLayout(self.right_layout)

        times = self.data_sources[self.model].getAvailableTimes()

        ## create dropdown menus
        models = sorted(self.data_sources.keys())
        self.model_dropdown = self.dropdown_menu(models)
        self.model_dropdown.setCurrentIndex(models.index(self.model))

        projs = [ ('npstere', 'Northern Hemisphere'), ('merc', 'Tropics'), ('spstere', 'Southern Hemisphere') ]
        if self.config.has_section('map'):
            proj = self.config.get('map', 'proj')
            proj_idx = zip(*projs)[0].index(proj)
        else:
            proj_idx = 0
        self.map_dropdown = self.dropdown_menu(zip(*projs)[1])
        self.map_dropdown.setCurrentIndex(proj_idx)

        self.run_dropdown = self.dropdown_menu([ t.strftime(Picker.run_format) for t in times ])
        try:
            self.run_dropdown.setCurrentIndex(times.index(self.run))
        except ValueError:
            print "Run dropdown is missing its times ... ?"
            print times

        ## connect the click actions to functions that do stuff
        self.model_dropdown.activated.connect(self.get_model)
        self.map_dropdown.activated.connect(self.get_map)
        self.run_dropdown.activated.connect(self.get_run)

        ## Create text labels to describe the various menus
        self.type_label = QLabel("Select Sounding Source")
        self.date_label = QLabel("Select Forecast Time")
        self.map_label = QLabel("Select Map Area")
        self.run_label = QLabel("Select Cycle")
        self.date_label.setDisabled(True)

        ## add the elements to the left side of the GUI
        self.left_layout.addWidget(self.type_label)
        self.left_layout.addWidget(self.model_dropdown)
        self.left_layout.addWidget(self.run_label)
        self.left_layout.addWidget(self.run_dropdown)
        self.left_layout.addWidget(self.date_label)
        self.left_layout.addWidget(self.profile_list)
        self.left_layout.addWidget(self.all_profs)
        self.left_layout.addWidget(self.button)

        ## add the elements to the right side of the GUI
        self.right_layout.setColumnMinimumWidth(0, 500)
        self.right_layout.addWidget(self.map_label, 0, 0, 1, 1)
        self.right_layout.addWidget(self.save_view_button, 0, 1, 1, 1)
        self.right_layout.addWidget(self.map_dropdown, 1, 0, 1, 2)
        self.right_layout.addWidget(self.view, 2, 0, 1, 2)

        ## add the left and right sides to the main window
        self.layout.addWidget(self.left_data_frame, 0, 0, 1, 1)
        self.layout.addWidget(self.right_map_frame, 0, 1, 1, 1)
        self.left_data_frame.setMaximumWidth(280)

    def create_map_view(self):
        """
        Create a clickable map that will be displayed in the GUI.
        Will eventually be re-written to be more general.

        Returns
        -------
        view : QWebView object
        """

        view = MapWidget(self.data_sources[self.model], self.run, self.async, width=800, height=500, cfg=self.config)
        view.clicked.connect(self.map_link)

        return view

    def dropdown_menu(self, item_list):
        """
        Create and return a dropdown menu containing items in item_list.

        Params
        ------
        item_list : a list of strings for the contents of the dropdown menu

        Returns
        -------
        dropdown : a QtGui.QComboBox object
        """
        ## create the dropdown menu
        dropdown = QComboBox()
        ## set the text as editable so that it can have centered text
        dropdown.setEditable(True)
        dropdown.lineEdit().setReadOnly(True)
        dropdown.lineEdit().setAlignment(Qt.AlignCenter)

        ## add each item in the list to the dropdown
        for item in item_list:
            dropdown.addItem(item)

        return dropdown

    def update_list(self):
        """
        Update the list with new dates.

        :param list:
        :return:
        """

        if self.select_flag:
            self.select_all()
        self.profile_list.clear()
        self.prof_idx = []
        timelist = []

        fcst_hours = self.data_sources[self.model].getForecastHours()
        if fcst_hours != [ 0 ]:
            self.profile_list.setEnabled(True)
            self.all_profs.setEnabled(True)
            self.date_label.setEnabled(True)
            for fh in fcst_hours:
                fcst_str = (self.run + date.timedelta(hours=fh)).strftime(Picker.date_format) + "   (F%03d)" % fh
                timelist.append(fcst_str)
        else:
            self.profile_list.setDisabled(True)
            self.all_profs.setDisabled(True)
            self.date_label.setDisabled(True)

        for item in timelist:
            self.profile_list.addItem(item)
 
        self.profile_list.update()
        self.all_profs.setText("Select All")
        self.select_flag = False

    def update_run_dropdown(self):
        """
        Updates the dropdown menu that contains the model run
        information.
        :return:
        """

        getTimes = lambda: self.data_sources[self.model].getAvailableTimes()
        
        def update(times):
            times = times[0]
            self.run_dropdown.clear()

            if self.model == "Observed":
                self.run = [ t for t in times if t.hour in [ 0, 12 ] ][-1]
            else:
                self.run = times[-1]

            for data_time in times:
                self.run_dropdown.addItem(data_time.strftime(Picker.run_format))

            self.run_dropdown.update()
            self.run_dropdown.setCurrentIndex(times.index(self.run))

        self.async_id = self.async.post(getTimes, update)

    def map_link(self, point):
        """
        Change the text of the button based on the user click.
        """
        if point is None:
            self.loc = None
            self.disp_name = None
            self.button.setText('Generate Profiles')
            self.button.setDisabled(True)
        else:
            self.loc = point #url.toString().split('/')[-1]
            if point['icao'] != "":
                self.disp_name = point['icao']
            elif point['iata'] != "":
                self.disp_name = point['iata']
            else:
                self.disp_name = point['srcid'].upper()

            self.button.setText(self.disp_name + ' | Generate Profiles')
            if self.has_connection:
                self.button.setEnabled(True) 

    @crasher(exit=False)
    def complete_name(self):
        """
        Handles what happens when the user clicks a point on the map
        """
        if self.loc is None:
            return
        else:
            self.prof_idx = []
            selected = self.profile_list.selectedItems()
            for item in selected:
                idx = self.profile_list.indexFromItem(item).row()
                if idx in self.prof_idx:
                    continue
                else:
                    self.prof_idx.append(idx)

            fcst_hours = self.data_sources[self.model].getForecastHours()

            if fcst_hours != [0] and len(self.prof_idx) > 0 or fcst_hours == [0]:
                self.prof_idx.sort()
                self.skewApp()

    def get_model(self, index):
        """
        Get the user's model selection
        """
        self.model = self.model_dropdown.currentText()

        self.update_run_dropdown()
        self.async.join(self.async_id)

        self.update_list()
        self.view.setDataSource(self.data_sources[self.model], self.run)

    def get_run(self, index):
        """
        Get the user's run hour selection for the model
        """
        self.run = date.datetime.strptime(self.run_dropdown.currentText(), Picker.run_format)
        self.view.setCurrentTime(self.run)
        self.update_list()

    def get_map(self):
        """
        Get the user's map selection
        """
        proj = {'Northern Hemisphere':'npstere', 'Tropics':'merc', 'Southern Hemisphere':'spstere'}[self.map_dropdown.currentText()]
        self.view.setProjection(proj)

    def save_view(self):
        """
        Save the map projection to the config file
        """
        self.view.saveProjection(self.config)

    def select_all(self):
        items = self.profile_list.count()
        if not self.select_flag:
            for i in range(items):
                if self.profile_list.item(i).text() in self.prof_idx:
                    continue
                else:
                    self.profile_list.item(i).setSelected(True)
            self.all_profs.setText("Deselect All")
            self.select_flag = True
        else:
            for i in range(items):
                self.profile_list.item(i).setSelected(False)
            self.all_profs.setText("Select All")
            self.select_flag = False

    def skewApp(self, filename=None):
        """
        Create the SPC style SkewT window, complete with insets
        and magical funtimes.
        :return:
        """

        profs = []
        dates = []
        failure = False

        exc = ""

        ## if the profile is an archived file, load the file from
        ## the hard disk
        if filename is not None:
            model = "Archive"
            prof_collection, stn_id = self.loadArchive(filename)
            disp_name = stn_id
            prof_idx = range(len(dates))

            run = prof_collection.getCurrentDate()
            fhours = None
            observed = True
        else:
        ## otherwise, download with the data thread
            prof_idx = self.prof_idx
            disp_name = self.disp_name
            run = self.run
            model = self.model
            observed = self.data_sources[model].isObserved()

            if self.data_sources[model].getForecastHours() == [ 0 ]:
                prof_idx = [ 0 ]

            ret = loadData(self.data_sources[model], self.loc, run, prof_idx)

            if isinstance(ret[0], Exception):
                exc = ret[0]
                failure = True
            else:
                prof_collection = ret[0]

            fhours = [ "F%03d" % fh for idx, fh in enumerate(self.data_sources[self.model].getForecastHours()) if idx in prof_idx ]

        if not failure:
            prof_collection.setMeta('model', model)
            prof_collection.setMeta('run', run)
            prof_collection.setMeta('loc', disp_name)
            prof_collection.setMeta('fhour', fhours)
            prof_collection.setMeta('observed', observed)

            if not observed:
                # If it's not an observed profile, then generate profile objects in background.
                prof_collection.setAsync(Picker.async)

            if self.skew is None:
                # If the SPCWindow isn't shown, set it up.
                self.skew = SPCWindow(parent=self.parent(), cfg=self.config)
                self.skew.closed.connect(self.skewAppClosed)
                self.skew.show()

            self.focusSkewApp()
            self.skew.addProfileCollection(prof_collection)

    def skewAppClosed(self):
        """
        Handles the user closing the SPC window.
        """
        self.skew = None

    def focusSkewApp(self):
        if self.skew is not None:
            self.skew.activateWindow()
            self.skew.setFocus()
            self.skew.raise_()

    def loadArchive(self, filename):
        """
        Get the archive sounding based on the user's selections.
        Also reads it using the Decoders and gets both the stationID and the profile objects
        for that archive sounding.
        """

        try:
            dec = SPCDecoder(filename)
        except Exception as e:
            try:
                dec = BufDecoder(filename)
            except:
                raise IOError("Could not figure out the format of '%s'!" % filename)

        profs = dec.getProfiles()
        stn_id = dec.getStnId()

        return profs, stn_id

    def hasConnection(self):
        return self.has_connection
Esempio n. 3
0
class Meso1819Gui( QMainWindow ):
  timeCheck = Signal();                                                         # Signal for time checking; used if the user tries to create Skew-T before the request sounding time actually happens; i.e., sounding date is in future
  def __init__(self, parent = None):
    QMainWindow.__init__(self);                                                 # Initialize the base class
    self.setWindowTitle('Meso 18/19 Sounding Processor');                       # Set the window title
    self.src_dir     = None;                                                    # Set attribute for source data directory to None
    self.dst_dir     = None;                                                    # Set attribute for destination data directory to None
    self.dst_dirFull = None;                                                    # Set attribute for destination data directory to None
    self.iopName     = None;                                                    # Set attribute for the IOP name to None
    self.dateFrame   = None;                                                    # Set attribute for the date QFrame to None
    self.date        = None;
    self.date_str    = None;                                                    # Set attribute for date string
    self.skew        = None;                                                    # Set attribute for the skewt plot to None
    self.sndDataFile = None;                                                    # Set attribute for sounding data input file
    self.sndDataPNG  = None;                                                    # Set attribute for sounding image file
    self.ftpInfo     = None;                                                    # Set attribute for ftp info
    self.ranFTP      = False;                                                   # Boolean to check if FTP uploading has been tried
    self.config      = ConfigParser.RawConfigParser();                          # Initialize a ConfigParser; required for the SPCWidget
    self.timeCheck.connect( self.on_timeCheck );                                # Connect on_timeCheck method to the timeCheck signal
    if not self.config.has_section('paths'):                                    # If there is no 'paths' section in the parser
      self.config.add_section( 'paths' );                                       # Add a 'paths' section to the parser
    self.log = logging.getLogger( __name__ );                                   # Get a logger
    rfh = RotatingFileHandler(_logfile,maxBytes=_logsize,backupCount=_logcount);# Create rotating file handler
    rfhFMT = logging.Formatter( '%(asctime)s - ' + settings.log_fmt );          # Logger format
    rfh.setFormatter( rfhFMT )
    rfh.setLevel( logging.DEBUG );                                              # Set log level to debug
    self.log.addHandler( rfh );                                                 # Add handler to main logger
    self.initUI();                                                              # Run method to initialize user interface
  ##############################################################################
  def initUI(self):
    '''
    Method to setup the buttons/entries of the Gui
    '''
    self.dateFrame    = dateFrame( );                                           # Initialize the dateFrame
    self.iopLabel     = QLabel('IOP Number');                                   # Initialize Entry widget for the IOP name
    self.iopName      = QLineEdit();                                            # Initialize Entry widget for the IOP name
    self.stationLabel = QLabel('Station Name');                                 # Initialize Entry widget for the IOP name
    self.stationName  = QLineEdit();                                            # Initialize Entry widget for the IOP name
    self.sourceButton = QPushButton('Source Directory');                        # Initialize button for selecting the source directory
    self.destButton   = QPushButton('Destination Directory');                   # Initialize button for selecting the destination directory
    self.sourcePath   = QLineEdit('');                                          # Initialize entry widget that will display the source directory path
    self.destPath     = QLineEdit('');                                          # Initialize entry widget that will display the destination directory path
    self.sourceSet    = indicator();                                            # Initialize an indictor that will appear when the source path is set
    self.destSet      = indicator();                                            # Initialize an indictor that will appear when the destination path is set
    
    self.sourcePath.setEnabled( False );                                        # Disable the sourcePath widget; that way no one can manually edit it
    self.destPath.setEnabled(   False );                                        # Disable the destPath widget; that way no one can manually edit it

    self.sourcePath.hide();                                                     # Hide the source directory path
    self.destPath.hide();                                                       # Hide the destination directory path
    self.sourceSet.hide();                                                      # Hide the source directory indicator
    self.destSet.hide();                                                        # Hide the destination directory indicator

    self.sourceButton.clicked.connect( self.select_source );                    # Set method to run when the source button is clicked 
    self.destButton.clicked.connect(   self.select_dest   );                    # Set method to run when the destination button is clicked

    self.copyButton = QPushButton( 'Copy Files' );                              # Create 'Copy Files' button
    self.copyButton.clicked.connect( self.copy_files );                         # Set method to run when 'Copy Files' button is clicked
    self.copyButton.setEnabled(False);                                          # Set enabled state to False; cannot click until after the source and destination directories set
    self.copySucces = indicator();                                              # Initialize an indictor that will appear when the copy complete successfuly
    self.copySucces.hide();

    self.procButton = QPushButton( 'Process Files' );                           # Create 'Process Files' button
    self.procButton.clicked.connect( self.proc_files );                         # Set method to run when 'Process Files' button is clicked
    self.procButton.setEnabled(False);                                          # Set enabled state to False; cannot click until after 'Copy Files' completes
    self.procSucces = indicator();                                              # Initialize an indictor that will appear when the processing complete successfuly
    self.procSucces.hide();

    self.genButton = QPushButton( 'Generate Sounding' );                        # Create 'Generate Sounding' button
    self.genButton.clicked.connect( self.gen_sounding );                        # Set method to run when 'Generate Sounding' button is clicked
    self.genButton.setEnabled(False);                                           # Set enabled state to False; cannot click until after 'Process Files' completes
    self.genSucces = indicator();                                               # Initialize an indictor that will appear when the sounding generation complete successfuly
    self.genSucces.hide();

    self.uploadButton = QPushButton( 'FTP Upload' );                            # Create 'FTP Upload' button
    self.uploadButton.clicked.connect( self.ftp_upload );                       # Set method to run when 'FTP Upload' button is clicked
    self.uploadButton.setEnabled(False);                                        # Set enabled state to False; cannot click until after 'Generate Sounding' completes
    self.uploadSucces = indicator();                                            # Initialize an indictor that will appear when the ftp upload complete successfuly
    self.uploadSucces.hide();

    self.checkButton = QPushButton( 'Check website' );                          # Create 'Check website' button
    self.checkButton.clicked.connect( self.check_site );                        # Set method to run when 'Check website' button is clicked
    self.checkButton.setEnabled(False);                                         # Set enabled state to False; cannot click until after 'FTP Upload' completes

    self.resetButton = QPushButton( 'Reset' );                                  # Create 'Check website' button
    self.resetButton.clicked.connect( self.reset_values );                      # Set method to run when 'Check website' button is clicked
    
    versionLabel = QLabel( 'version: {}'.format(__version__) );                 # Version label
    versionLabel.setAlignment( Qt.AlignHCenter );                               # Set alignment to center
    log_handler  = QLogger( );                                                  # Initialize a QLogger logging.Handler object
    logging.getLogger('Meso1819').addHandler( log_handler );                    # Get the Meso1819 root logger and add the handler to it

    grid = QGridLayout();                                                       # Initialize grid layout
    grid.setSpacing(10);                                                        # Set spacing to 10
    for i in range(4): 
      grid.setColumnStretch(i,  0);                                             # Set column stretch for ith column
      grid.setColumnMinimumWidth(i,  60);                                       # Set column min width for ith column
    grid.setColumnStretch(4,  0);                                               # Set column stretch for 5th column
    grid.setColumnMinimumWidth(4,  20);                                         # Set column min width for 5th column

    grid.setRowStretch(1,  0);                                                  # Set column stretch for 5th column
    grid.setRowStretch(3,  0);                                                  # Set column stretch for 5th column
    grid.setRowMinimumHeight(1,  25);                                           # Set column min width for 5th column
    grid.setRowMinimumHeight(3,  25);                                           # Set column min width for 5th column
    
    grid.addWidget( self.sourceButton,  0, 0, 1, 4 );                           # Place a widget in the grid
    grid.addWidget( self.sourceSet,     0, 4, 1, 1 );                           # Place a widget in the grid
    grid.addWidget( self.sourcePath,    1, 0, 1, 5 );                           # Place a widget in the grid

    grid.addWidget( self.destButton,    2, 0, 1, 4 );                           # Place a widget in the grid
    grid.addWidget( self.destSet,       2, 4, 1, 1 );                           # Place a widget in the grid
    grid.addWidget( self.destPath,      3, 0, 1, 5 );                           # Place a widget in the grid

    grid.addWidget( self.iopLabel,      4, 0, 1, 2 );                           # Place a widget in the grid
    grid.addWidget( self.iopName,       5, 0, 1, 2 );                           # Place a widget in the grid

    grid.addWidget( self.stationLabel,  4, 2, 1, 2 );                           # Place a widget in the grid
    grid.addWidget( self.stationName,   5, 2, 1, 2 );                           # Place a widget in the grid

    grid.addWidget( self.dateFrame,     6, 0, 1, 4 );                           # Place a widget in the grid
    grid.addWidget( self.copyButton,    7, 0, 1, 4 );                           # Place a widget in the grid
    grid.addWidget( self.copySucces,    7, 4, 1, 1 );                           # Place a widget in the grid
    grid.addWidget( self.procButton,    8, 0, 1, 4 );                           # Place a widget in the grid
    grid.addWidget( self.procSucces,    8, 4, 1, 1 );                           # Place a widget in the grid
    grid.addWidget( self.genButton,     9, 0, 1, 4 );                           # Place a widget in the grid
    grid.addWidget( self.genSucces,     9, 4, 1, 1 );                           # Place a widget in the grid
    grid.addWidget( self.uploadButton, 10, 0, 1, 4 );                           # Place a widget in the grid
    grid.addWidget( self.uploadSucces, 10, 4, 1, 1 );                           # Place a widget in the grid
    grid.addWidget( self.checkButton,  11, 0, 1, 4 );                           # Place a widget in the grid
    grid.addWidget( self.resetButton,  12, 0, 1, 4 );                           # Place a widget in the grid

    grid.addWidget( log_handler.frame, 0, 6, 13, 1);
    grid.addWidget( versionLabel, 20, 0, 1, 7)
    centralWidget = QWidget();                                                  # Create a main widget
    centralWidget.setLayout( grid );                                            # Set the main widget's layout to the grid
    self.setCentralWidget(centralWidget);                                       # Set the central widget of the base class to the main widget
    
    self.show( );                                                               # Show the main widget
  ##############################################################################
  def select_source(self, *args):
    '''
    Method for selecting the source directory of the
    sounding data that was collected
    '''
    self.log.info('Setting the source directory')
    src_dir = QFileDialog.getExistingDirectory( dir = _desktop );               # Open a selection dialog
    self.src_dir = None if src_dir == '' else src_dir;                          # Update the src_dir attribute based on the value of src_dir

    if self.src_dir is None:                                                    # If the src_dir attribute is None
      self.log.warning( 'No source directory set' );                            # Log a warning
      self.reset_values( noDialog = True );                                     # Reset all the values in the GUI with no confirmation dialog
    else:                                                                       # Else
      self.sourcePath.setText( src_dir );                                       # Set the sourcePath label text
      self.sourcePath.show();                                                   # Show the sourcePath label
      self.sourceSet.show();                                                    # Show the sourceSet icon
      if self.dst_dir is not None:                                              # If the dst_dir attribute is not None
        self.copyButton.setEnabled( True );                                     # Set the 'Copy Files' button to enabled
        self.reset_values(noDialog = True, noSRC = True, noDST = True);         # Reset all values excluding the src AND dst directory
      else:
        self.reset_values(noDialog = True, noSRC = True);                       # Reset all values excluding the src directory

  ##############################################################################
  def select_dest(self, *args):
    '''
    Method for selecting the destination directory of the
    sounding data that was collected
    '''
    self.log.info('Setting the destination directory')
    dst_dir = QFileDialog.getExistingDirectory( dir = _desktop);                # Open a selection dialog
    self.dst_dir = None if dst_dir == '' else dst_dir;                          # Update the dst_dir attribute based on the value of dst_dir
                                                                                
    if self.dst_dir is None:                                                    # If the dst_dir attribute is None
      self.log.warning( 'No destination directory set' )                        # Log a warning
      self.reset_values( noDialog = True )                                      # Reset all the values in the GUI with no confirmation dialog
    else:                                                                       # Else
      if 'IOP' in os.path.basename( self.dst_dir ).upper():                     # If an IOP directory was selected
        self.log.debug('Moved IOP# from directory path as it is append later'); # Log some debugging information
        self.dst_dir = os.path.dirname( self.dst_dir );                         # Remove the IOP directory from the destination directory
      self.destSet.show( );                                                     # Set the destPath label text
      self.destPath.setText( self.dst_dir )                                     # Show the destPath label
      self.destPath.show()                                                      # Show the destSet icon
      if self.src_dir is not None:                                              # If the src_dir attribute is not None
        self.copyButton.setEnabled( True );                                     # Set the 'Copy Files' button to enabled
        self.reset_values(noDialog = True, noSRC = True, noDST = True);         # Reset all values excluding the src AND dst directory
      else:
        self.reset_values(noDialog = True, noDST = True);                       # Reset all values excluding the dst directory
  ##############################################################################
  def copy_files(self, *args):
    '''
    Method for copying files from source to destination, renaming
    files along the way
    '''
    if self.dst_dir is None: 
      self.log.error( 'Destination directory NOT set!' );
      return;
    if self.src_dir  is None:
      self.log.error( 'Source directory NOT set!' );
      return;

    if self.iopName.text() == '':
      self.log.error( 'IOP Number NOT set!!!' )
      criticalMessage( "Must set the IOP Number!!!" ).exec_();
      return
    if self.stationName.text() == '':
      self.log.error( 'Station Name NOT set!!!' )
      criticalMessage( "Must set the Station Name!!!" ).exec_();
      return

    # Main copying code
    failed = False;                                                             # Initialize failed to False    
    self.__init_ftpInfo();                                                      # Initialize ftpInfo attribute using method
    self.date, self.date_str = self.dateFrame.getDate( );                       # Get datetime object and date string as entered in the gui
    if self.date is None: return;                                               # If the date variable is set to None
    self.dst_dirFull  = os.path.join( 
      self.dst_dir, 'IOP'+self.iopName.text(), self.date_str
    );                                                                          # Build destination directory using the dst_dir, iopName, and date string
    if not os.path.isdir( self.dst_dirFull ):                                   # If the output directory does NOT exist
      self.log.info( 'Creating directory: ' + self.dst_dirFull );               # Log some information
      os.makedirs( self.dst_dirFull );                                          # IF the dst_dir does NOT exist, then create it
    else:                                                                       # Else, the directory exists, so check to over write
      dial = confirmMessage( 
        "The destination directory exists!\n" + \
        "Do you want to overwrite it?\n\n" + \
        "YOU CANNOT UNDO THIS ACTION!!!" 
      );
      dial.exec_();                                                             # Generate the message window
      if dial.check():
        self.log.info( 'Removing directory: ' + self.dst_dirFull );             # Log some information
        shutil.rmtree( self.dst_dirFull );                                      # Delete the directory
        self.log.info( 'Creating directory: ' + self.dst_dirFull );             # Log some information
        os.makedirs( self.dst_dirFull );                                        # IF the dst_dir does NOT exist, then create it
      else:                                                                     # Else, don't do anything
        self.log.warning('Cannot over write data!');                            # Log a warning
        return;                                                                 # Return from function

    self.log.info( 'Source directory: {}'.format(self.src_dir) );               # Log some information
    self.log.info( 'Destination directory: {}'.format(self.dst_dirFull) );      # Log some information
    self.log.info( 'Copying directory' );                                       # Log some information
    for root, dirs, files in os.walk( self.src_dir ):                           # Walk over the source directory
      for file in files:                                                        # Loop over all files
        src = os.path.join( root, file );                                       # Set the source file path
        dst = os.path.join( self.dst_dirFull, file );                           # Set the destination path
        shutil.copy2( src, dst );                                               # Copy all data from the source directory to the dst_dir
        if not os.path.isfile( dst ):                                           # If the destination file does NOT exist
          self.log.error( 'There was an error copying file: {}'.format(file) ); # Log a warning
          failed = True;                                                        # Set failed to True
          break;                                                                # Break the for loop
    if not failed:
      self.log.info( 'Finished copying' );                                      # log some information
      self.log.info( 'Ready to process data files!' );                          # Log some info
      self.copySucces.show();                                                   # Show green light next to the 'Copy Files' button to indicate that step is complete
      self.procButton.setEnabled( True );                                       # Enable the 'Process Files' button
    else:                                                                       # Else, something went wrong
      criticalMessage(
        "Something went wrong!\n\n" + \
        "There was an error copying a data file.\n" + \
        "Please check the logs and directories to see what happened."
      ).exec_(); 

  ##############################################################################
  def proc_files(self, *args):
    '''
    Method for processing sounding files;
      i.e., renaming and removing values where ballon is descending in
      sounding
    '''
    failed = False;                                                             # Initialize failed to False
    self.log.info( 'Processing files' );    
    files = os.listdir( self.dst_dirFull );                                     # Get list of all files in the directory
    rename_status  = dict.fromkeys( settings.rename.keys(),  False);            # Status of file renaming
    process_status = dict.fromkeys( settings.convert.keys(), False);            # Status of file processing

    for file in files:                                                          # Iterate over the list of files
      for key in settings.rename:                                               # Loop over the keys in the settings.rename dictionary
        if key in file:                                                         # If the key is in the source file name
          rename_status[key] = True;                                            # Add rname status as True
          dst_file = settings.rename[key].format( self.date_str );              # Set a destination file name
          dst      = os.path.join( self.dst_dirFull, dst_file );                # Build the destination file path
          src      = os.path.join( self.dst_dirFull, file );                    # Set source file path
#           self.uploadFiles.append( dst );                                     # Append the file to the uploadFile list
          self.log.info( 'Moving file: {} -> {}'.format(src, dst) );            # Log some information
          os.rename( src, dst );                                                # Move the file
          if not os.path.isfile( dst ):                                         # If the renamed file does NOT exist
            self.log.error( 'There was an error renaming the file!' );          # Log an error
            failed = True;                                                      # Set failed to True
      
      file_found = False;                                                       # Flag for in the file to process is found!
      for key in settings.convert:                                              # Loop over the keys in the settings.rename dictionary
        if key in file:                                                         # If the key is in the source file name
          process_status[key] = True;                                           # Set to true if the file is found
          dst_file = settings.convert[key].format( self.date_str );             # Set a destination file name
          self.sndDataFile = os.path.join( self.dst_dirFull, dst_file );        # Build the destination file path
          src              = os.path.join( self.dst_dirFull, file );            # Set source file path
#           self.uploadFiles.append( dst );                                     # Append the file to the uploadFile list
          
          self.log.info( 'Converting sounding data to SHARPpy format...' );     # Log some information
          res = iMet2SHARPpy( src, self.stationName.text().upper(), 
            datetime = self.date, output = self.sndDataFile);                   # Run function to convert data to SHARPpy format
          if res and os.path.isfile( self.sndDataFile ):                        # If function returned True and the output file exists
            for key in self.ftpInfo:                                            # Iterate over keys in ftpInfo attribute
              self.ftpInfo[key]['files'].append( self.sndDataFile );            # Append sounding data file path to files key in ftpInfo dictionary
          else:
            failed = True;                                                      # Set failed to True
            self.sndDataFile = None;                                            # if the function failed to run OR the output file does NOT exist
            self.log.error( 'There was an error creating SHARPpy file!' );      # Log an error
            criticalMessage(
              'Problem converting the sounding data to SHARPpy format!'
            ).exec_();                                                          # Generate critical error message box
    
    if not all( rename_status.values() ):
      failed = True;
      self.log.error( 'There was an error renaming one or more files!' );       # Log an error
      criticalMessage(
        'Problem renaming one or more files!'
      ).exec_();                                                          # Generate critical error message box

    if not all( process_status.values() ):
      failed = True;
      self.log.error( 'There was an error converting one or more files to SHARPpy format!' );       # Log an error
      criticalMessage(
        'Problem converting one or more files to SHARPpy format!'
      ).exec_();                                                          # Generate critical error message box

    if not failed:                                                              # If failed is False
      self.procSucces.show();                                                   # Show green light next to the 'Process Files' button to indicate that step is complete
      if self.date <= datetime.utcnow():                                        # If the date for the sound is NOT in the future
        self.timeCheck.emit();                                                  # Emit signal to activate the 'Generate Sounding' button
      else:                                                                     # Else, date for sounding IS in the future
        dt  = self.date - datetime.utcnow();                                    # Compute the current time difference between the sounding and utcnow
        msg = ['Date requested is in the future!', 
               'Sounding generation disabled until requested sounding time',
               'Wait time remaining {}'.format( str(dt) ) ];                    # List that contains message for the logger
        self.log.warning( '\n'.join(msg) );                                     # Log the message as a warning
        criticalMessage(
          'The data processing has completed!\n\n' + \
          'However, the requested date/time for the\n' + \
          'sounding is in the future!\n\n' +\
          'The \'Generate Sounding\' button will activate\n' + \
          'when the current time is after the requested time!'
        ).exec_();                                                              # Generate critical error message box
        dt = (int( dt.total_seconds() ) + 2) * 1000;                            # Get total time between now and future time, add 2 seconds, convert to integer, then conver to milliseconds
        QTimer.singleShot( dt, self._timeCheck );                               # Run single shot timer thread for the _timeCheck method, waiting dt milliseconds before running
  ##############################################################################
  def gen_sounding(self, *args):
    '''
    Method for generating the SPC-like sounding using the
    SPCWindow class of SHARPpy.
    '''
    self.log.info( 'Generating Skew-T diagram' );                               # Log some information
    sndDataPNG      = settings.skewT_fmt.format( self.date_str );               # Set the name for the skewT file using the settings.skew_T_fmt string
    self.sndDataPNG = os.path.join( self.dst_dirFull, sndDataPNG );             # Set the sndDataPNG attribute using the dst_dirFull attribute and sndDataFile variable

    save_msg = "Check that the image looks okay.\n " + \
      "If ok, click save, else click cancel";                                   # Confirmation message for the save dialog for Skew-T; will update if cannot save Skew-T due to issue in SHARPpy
    sharppy_bug = False;                                                        # Flag for if sharppy bug encountered
    try:                                                                        # Try to...
      decoder = SPCDecoder( self.sndDataFile );                                 # Decode the sounding file using the SPCDecoder
      profile = decoder.getProfiles();                                          # Get the profiles from the file
      stn_id  = decoder.getStnId();                                             # Get the station id from the file
    except:                                                                     # On exception
      criticalMessage( 
        "There was an error loading the sounding data\n\n"
      ).exec_();                                                                # Initialize and display critical error dialog
      return;                                                                   # Return from method
    model     = "Archive";                                                      # Set model to 'Archive'; not sure why but was in the SHARPpy full_gui.py 
    disp_name = stn_id;                                                         # Set the display name to the station ID from the sounding data file
    run       = profile.getCurrentDate();                                       # Set the run to the current date from the sounding data
    profile.setMeta('model', model);                                            # Set the model in the sounding data
    profile.setMeta('loc',   disp_name);                                        # Set the display name in the sounding data
    profile.setMeta('run',   run);                                              # Set the run in the sounding data
    
    if not profile.getMeta('observed'):                                         # If it's not an observed profile
      profile.setAsync( AsyncThreads(2, debug) );                               # Generate profile objects in background. Not sure why works but in SHARPpy full_gui.py
    
    self.log.debug('Generating SHARPpy window')
    if self.skew is None:                                                       # If there is no skew window setup; there should never be...
      self.skew = SPCWindow(cfg=self.config);                                   # Initialize a new SPCWindow object
      self.skew.closed.connect(self.__skewAppClosed);                           # Connect the closed method to the __skewAppClosed private method
      self.skew.addProfileCollection(profile);                                  # Add the profile data to the SPCWindow object
      try:
        self.skew.show();                                                         # Show the window
      except:
        sharppy_bug = True;
        self.log.warning("SHARPpy didn't like that sounding very much!")
        save_msg = "Congradulations!\n\n"   + \
          "You just found a bug in SHARPpy.\n" + \
          "There is nothing we can about this. No Skew-T can be created.\n" + \
          "Just click 'Save' and continue with the uploading";
    dial = saveMessage( save_msg );                                             # Set up save message pop-up
    dial.exec_();                                                               # Display the save message pop-up
    if dial.check():                                                            # If clicked save
      if not sharppy_bug:                                                       # If the SHARPpy bug did NOT occur
        self.ftpInfo['ucar']['files'].append( self.sndDataPNG );                # Append the SHARPpy image file name to the ftpInfo['ucar']['files'] list
        self.log.info('Saving the Skew-T to: {}'.format( self.sndDataPNG ) );   # Log some information
        pixmap = QPixmap.grabWidget( self.skew );                               # Grab the image from the skew T window
        pixmap.save( self.sndDataPNG, 'PNG', 100);                              # Save the image
        self.config.set('paths', 'save_img', os.path.dirname(self.sndDataPNG)); # Add image path to the config object

      self.log.debug( 'Files to upload to UCAR: {}'.format( 
        ', '.join(self.ftpInfo['ucar']['files']) ) )
      self.genSucces.show();                                                    # Show green light next to the 'Generate Sounding' button to indicate that step is complete
      self.uploadButton.setEnabled( True );                                     # Enable the upload button
    else:                                                                       # Else
      self.log.critical('Skew-T save aborted! Not allowed to upload!');         # Log an error
    try:
      self.skew.close();                                                        # Close the skew T window
    except:
      pass;
  ##############################################################################
  def ftp_upload(self, *args):
    if self.ranFTP:
      self.log.info( 'You already ran this step!' );
      criticalMessage( "You already tried to upload to FTP" ).exec_();          # Initialize confirmation for quitting
      return;
    self.ranFTP = True;                                                         # Set ran FTP to True
    for key in self.ftpInfo:                                                    # Iterate over all keys in the ftpInfo dictionary
      self.log.info( 'Uploading data to: {}'.format(self.ftpInfo[key]['url']) );# Log some info
      try:                                                                      # Try to...
        ftp = ftpUpload( self.ftpInfo[key]['url'] );                            # Set up and FTP instance
      except:                                                                   # On exception...
        self.log.critical( 
          'Error initializing the FTP upload: {}'.format(
            self.ftpInfo[key]['url']
          )
        );                                                                      # Log a critical error
      else:                                                                     # Else...
        self.ftpInfo[key]['upload'] = ftp.uploadFiles(
          self.ftpInfo[key]['dir'], 
          self.ftpInfo[key]['files'], 
          user   = self.ftpInfo[key]['user'],
          passwd = self.ftpInfo[key]['passwd'],
        );                                                                      # Attempt to upload the files
      if not self.ftpInfo[key]['upload']:                                       # If one or more files failed to upload
        msg = 'FAILED!!! Upload to {} NOT successful';                          # Formatter for error message
        self.log.critical( msg.format( self.ftpInfo[key]['url'] ) );            # Log a critical error
        criticalMessage( 
          "Something went wrong!\n\n" + \
          "There was an error uploading one or more files to the FTP.\n\n"  + \
          "FTP Address: {}\n\n".format( self.ftpInfo[key]['url'] )          + \
          "Check the logs to determine which file(s) failed to upload.\n\n" + \
          "YOU MUST MANUALLY UPLOAD ANY FILES THAT FAILED!!!"
        ).exec_();
      else:
        self.log.info( 'Data upload successful!' );
    if self.ftpInfo['ucar']['upload']:                                          # If the upload to UCAR was a success
      self.uploadSucces.show();                                                 # Show green light next to the 'FTP Upload' button to indicate that step is complete
      self.checkButton.setEnabled(True)
  ##############################################################################
  def check_site(self, *args):
    self.log.info( 'Checking site' )    
    webbrowser.open( settings.url_check )
  ##############################################################################
  def reset_values(self, noDialog = False, noSRC = False, noDST = False):
    '''
    Method to reset all the values in the GUI
    Keywords:
       noDialog  : Set to true to disable the checking dialog
       noSRC     : Set to true so that all values BUT source directory info are cleared
       noDST     : Set to true so that all values BUT destination directory info are cleared
       
       Setting both noSRC and noDST will exclude the two directories
    '''
    check = False;                                                              # Dialog check value initialize to False
    if not noDialog:                                                            # If noDialog is False
      dial = confirmMessage( 'Are you sure you want to reset all values?' );    # Initialize confirmation dialog
      dial.exec_();                                                             # Display the confirmation dialog
      check = dial.check();                                                     # Check which button selected
    if check or noDialog:                                                       # If the check is True or noDialog is True
      self.log.debug( 'Resetting all values!' );                                # Log some information
      if not noSRC or not noDST:                                                # If noSRC is False OR noDST is false
        self.copyButton.setEnabled( False );                                    # Set enabled state to False; cannot click until after the source and destination directories set
      self.procButton.setEnabled(   False );                                    # Set enabled state to False; cannot click until after 'Copy Files' completes
      self.genButton.setEnabled(    False );                                    # Set enabled state to False; cannot click until after 'Process Files' completes
      self.uploadButton.setEnabled( False );                                    # Set enabled state to False; cannot click until after 'Generate Sounding' completes
      self.checkButton.setEnabled(  False );                                    # Set enabled state to False; cannot click until after 'FTP Upload' completes
      
      self.copySucces.hide();
      self.procSucces.hide();
      self.genSucces.hide()
      self.uploadSucces.hide();
  
      if not noSRC:                                                             # If the noSRC keyword is NOT set
        self.sourcePath.hide();                                                 # Hide the source directory path
        self.sourceSet.hide();                                                  # Hide the source directory indicator
      if not noDST:                                                             # If the noDST keyword is NOT set
        self.destPath.hide();                                                   # Hide the destination directory path
        self.destSet.hide();                                                    # Hide the destination directory indicator
  
      self.dateFrame.resetDate();                                               # Reset all the dates
      self.iopName.setText(     '' );                                           # Initialize Entry widget for the IOP name
      self.stationName.setText( '' );                                           # Initialize Entry widget for the IOP name
      self.ranFTP = False;                                                      # Reset ranFTP to False on reset
      self.__reset_ftpInfo();
  ##############################################################################
  def closeEvent(self, event):
    '''
    Method to override closeEvent handler to add a confirmation dialog
    to application quit
    '''
    dial = confirmMessage( "Are you sure you want to quit?" );                  # Initialize confirmation for quitting
    dial.exec_();                                                               # Generate the message window
    if dial.check():                                                            # If user selected yes on dialog
      event.accept();                                                           # Accept the event and close the GUI
    else:                                                                       # Else
      event.ignore();                                                           # Ignore the event; i.e., keep GUI open
  ##############################################################################
  @Slot()
  def on_timeCheck(self):
    self.log.info( 'Ready to generate sounding image!' );                       # Log some info
    self.genButton.setEnabled( True );                                          # Enable the 'Generate Sounding' button
  ##############################################################################
  def _timeCheck(self):
    if self.date <= datetime.utcnow():                                          # Check requested date is in the future AND the timeEvent is NOT set
      self.timeCheck.emit();                                                    # Emit a signal to enable the 'Generate Sounding' button
    else:                                                                       # Else, date is still in future...
      QTimer.singleShot( 1000, self._timeCheck );                               # Wait one second and call this method again
  ##############################################################################
  def __skewAppClosed(self):
    self.skew = None;
  ##############################################################################
  def __init_ftpInfo(self):
    self.ftpInfo = {'ucar' : settings.ucar_ftp, 
                    'noaa' : settings.noaa_ftp};
    for key in self.ftpInfo:
      self.ftpInfo[key]['files']  = [];
      self.ftpInfo[key]['upload'] = False;
  ##############################################################################
  def __reset_ftpInfo(self):
    self.ftpInfo = None;