def run(self): """Makes a few sanity checks and prepares the worker thread.""" # create the dialog (after translation) and keep reference self.dialog = NaturalEarthRasterDialog() # connect some odds and ends self.dialog.scaleCombo.currentIndexChanged.connect(self.update_theme) self.dialog.themeCombo.currentIndexChanged.connect(self.update_preview) # make sure we have themes to choose from self.update_theme(0) # show the dialog self.dialog.show() result = self.dialog.exec_() if result == QDialog.Rejected: return False # get the indices of the selected combobox items scale = self.get_scale() theme = self.dialog.themeCombo.currentIndex() # build the path to the local copy of the theme id = self.themes[scale][theme]['id'] theme_path = os.path.join(self.plugin_dir, 'data', id, id + '.tif') if os.path.isfile(theme_path): # we have a local copy of the theme, just add it to the workspace self.add_raster_layer(theme_path, id) else: # we don't have a local copy, use a worker thread to download it url = self.themes[scale][theme]['url'] self.worker_start(self.plugin_dir, url, id)
class NaturalEarthRaster: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join( self.plugin_dir, 'i18n', '{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) self.themes = {'high': [], 'medium': [], 'low': []} # read theme metadata from local file csv_file = os.path.join(self.plugin_dir, 'data', 'natural_earth.csv') with open(csv_file, 'rb') as csvfile: reader = csv.reader(csvfile, delimiter=';') for row in reader: self.themes[row[0]].append({'id': row[1], 'name': row[2], 'preview': row[3], 'url': row[4]}) # declare instance attributes self.action = None self.menu = self.tr('&Natural Earth') def initGui(self): """Create the menu entries inside the QGIS GUI.""" # create action to display the settings dialog self.action = QAction( QIcon(':/plugins/natural_earth_raster/assets/icon.png'), self.tr('Add theme...'), self.iface.mainWindow()) # connect the actions to their respective methods self.action.triggered.connect(self.run) # add toolbar button and menu items self.iface.addPluginToRasterMenu(self.menu, self.action) def unload(self): """Removes the plugin menu item from the QGIS GUI.""" self.iface.removePluginRasterMenu('&Natural Earth', self.action) def run(self): """Makes a few sanity checks and prepares the worker thread.""" # create the dialog (after translation) and keep reference self.dialog = NaturalEarthRasterDialog() # connect some odds and ends self.dialog.scaleCombo.currentIndexChanged.connect(self.update_theme) self.dialog.themeCombo.currentIndexChanged.connect(self.update_preview) # make sure we have themes to choose from self.update_theme(0) # show the dialog self.dialog.show() result = self.dialog.exec_() if result == QDialog.Rejected: return False # get the indices of the selected combobox items scale = self.get_scale() theme = self.dialog.themeCombo.currentIndex() # build the path to the local copy of the theme id = self.themes[scale][theme]['id'] theme_path = os.path.join(self.plugin_dir, 'data', id, id + '.tif') if os.path.isfile(theme_path): # we have a local copy of the theme, just add it to the workspace self.add_raster_layer(theme_path, id) else: # we don't have a local copy, use a worker thread to download it url = self.themes[scale][theme]['url'] self.worker_start(self.plugin_dir, url, id) def worker_start(self, dir, url, id): """Start a worker instance on a background thread.""" worker = NaturalEarthRasterWorker(dir, url, id) message_bar = self.iface.messageBar().createMessage('') label = QLabel('Downloading theme {}...'.format(id)) label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) progress_bar = QProgressBar() progress_bar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) progress_bar.setMaximum(100) cancel_button = QPushButton() cancel_button.setText(self.tr('Cancel')) cancel_button.clicked.connect(worker.kill) message_bar.layout().addWidget(label) message_bar.layout().addWidget(progress_bar) message_bar.layout().addWidget(cancel_button) self.iface.messageBar().pushWidget(message_bar, self.iface.messageBar().INFO) self.message_bar = message_bar # start the worker in a new thread thread = QThread() worker.moveToThread(thread) # connect some odds and ends worker.finished.connect(self.worker_finished) worker.error.connect(self.worker_error) worker.progress.connect(progress_bar.setValue) thread.started.connect(worker.run) thread.start() self.thread = thread self.worker = worker def worker_finished(self, id): """Clean up after the worker and the thread.""" # kill the thread self.worker.deleteLater() self.thread.quit() self.thread.wait() self.thread.deleteLater() # remove the progress bar self.iface.messageBar().popWidget(self.message_bar) if id is not None: # add the downloaded layer to the workspace path = os.path.join(self.plugin_dir, 'data', id, id + '.tif') self.add_raster_layer(path, id) else: # display an error message in the message bar message = self.tr('Theme download cancelled by user.') self.iface.messageBar().pushMessage(message, level=QgsMessageBar.INFO, duration=3) def worker_error(self, e, exception_string): """Handle errors from the worker thread.""" # add the error message to the general log message = 'Worker thread exception: {}'.format(exception_string) QgsMessageLog.logMessage(message, level=QgsMessageLog.CRITICAL) def update_theme(self, index): """Update the theme selection combobox.""" # remove all existing items from the combobox self.dialog.themeCombo.clear() # add items from the selected scale to the combobox scale = self.get_scale() for theme in self.themes[scale]: self.dialog.themeCombo.addItem(theme['name']) def update_preview(self, index): """Update the preview label image.""" # get the filename of the preview image scale = self.get_scale() preview = self.themes[scale][index]['preview'] # load the image path = os.path.join(self.plugin_dir, 'assets', 'previews', preview) image = QPixmap(path) # add the image to the label self.dialog.previewLabel.setPixmap(image) def add_raster_layer(self, path, id): """Add a downloaded raster layer to the workspace.""" # create a new rasterlayer from the supplied TIF file layer = QgsRasterLayer(path, id) # load layer style based on number of bands bands = layer.bandCount() if bands == 1: path = os.path.join(self.plugin_dir, 'styles', 'singleband.qml') else: path = os.path.join(self.plugin_dir, 'styles', 'multiband.qml') layer.loadNamedStyle(path) # add the layer to the workspace QgsMapLayerRegistry.instance().addMapLayer(layer) def get_scale(self): """Get the name of a scale based on a combobox index.""" index = self.dialog.scaleCombo.currentIndex() try: return ['high', 'medium', 'low'][index] except IndexError: return default # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API.""" # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('Natural Earth Raster', message)