def on_program_execution_done(self, path: str, error: Union[bool, str, None]) -> None: self.wps_box.setVisible(True) self.wrf_box.setVisible(True) self.control_box.setVisible(False) # The above causes a resize of the program output textarea # which requires that we scroll to the bottom again. vert_scrollbar = self.stdout_textarea.verticalScrollBar() vert_scrollbar.setValue(vert_scrollbar.maximum()) if self.dont_report_program_status: return if error is None: # An exception that will be reported by the caller after this function returns. return if error: if isinstance(error, str): raise UserError('Program {} failed: {}'.format( os.path.basename(path), error)) else: raise UserError( 'Program {} failed with errors, check the logs'.format( os.path.basename(path))) else: QMessageBox.information( self.iface.mainWindow(), PLUGIN_NAME, 'Program {} finished without errors!'.format( os.path.basename(path)))
def run_convert_to_wps_binary(self) -> None: msg_bar = self.iface.messageBar() # type: QgsMessageBar layer = self.iface.activeLayer() # type: QgsMapLayer if layer is None: raise UserError('No layer selected, use the "Layers" panel') source = layer.source() if not os.path.exists(source): # Currently in-memory layers are not supported, but QGIS in most cases saves # layers to temporary files on disk during processing operations, so this is not a big issue. raise UnsupportedError( 'Only layers that exist on the filesystem are supported') reply = QMessageBox.question(self.iface.mainWindow(), 'Layer type', "Is this layer's data categorical?", QMessageBox.Yes, QMessageBox.No) is_categorical = reply == QMessageBox.Yes out_dir = QFileDialog.getExistingDirectory( caption='Select WPS Binary File Output Folder') if not out_dir: return if not ensure_folder_empty(out_dir, self.iface): return output = convert_to_wps_binary(source, out_dir, is_categorical, strict_datum=False) msg_bar.pushInfo( PLUGIN_NAME, 'WPS Binary Format files created in {}'.format(out_dir)) if output.datum_mismatch: msg_bar.pushWarning( PLUGIN_NAME, 'Input layer had an unexpected datum, no datum shift was performed. Expected: {}, Actual: {}' .format(output.datum_mismatch.expected, output.datum_mismatch.actual))
def create_domain_crs(self) -> CRS: proj = self.get_proj_kwargs() if proj is None: raise UserError('Incomplete projection definition') map_proj = proj['map_proj'] if map_proj == 'lambert': if self.center_lat.is_valid(): origin_lat = self.center_lat.value() else: origin_lat = 0 crs = CRS.create_lambert(proj['truelat1'], proj['truelat2'], LonLat(proj['stand_lon'], origin_lat)) elif map_proj == 'polar': crs = CRS.create_polar(proj['truelat1'], proj['stand_lon']) elif map_proj == 'mercator': if self.center_lon.is_valid(): origin_lon = self.center_lon.value() else: origin_lon = 0 crs = CRS.create_mercator(proj['truelat1'], origin_lon) elif map_proj == 'lat-lon': crs = CRS.create_lonlat() else: assert False, 'unknown proj: ' + map_proj return crs
def on_export_geogrid_namelist_button_clicked(self): if not self.update_project(): raise UserError('Domain configuration invalid, check fields') file_path, _ = QFileDialog.getSaveFileName(caption='Save WPS namelist as', \ directory='namelist.wps') if not file_path: return wps_namelist = convert_project_to_wps_namelist(self.project) write_namelist(wps_namelist, file_path)
def on_set_layer_extent_button_clicked(self): if not self.resolution.is_valid(): return layer = self.iface.activeLayer() # type: QgsMapLayer if layer is None: print('foo') raise UserError('No layer selected, use the "Layers" panel') layer_crs = layer.crs() # type: QgsCoordinateReferenceSystem extent = layer.extent() # type: QgsRectangle self.set_domain_to_extent(layer_crs, extent)
def on_download_button_clicked(self): param_names = [] for index in range(self.tree.count()): item = self.tree.item(index) if item.checkState() == Qt.Checked: param_name = item.data(Qt.UserRole) param_names.append(param_name) dataset_name = self.cbox_dataset.currentData() product_name = self.cbox_product.currentData() start_date = self.dedit_start_date.dateTime().toPyDateTime() end_date = self.dedit_end_date.dateTime().toPyDateTime() if dataset_name is None or product_name is None: raise UserError('Dataset/Product not selected') args = [ self.options.met_dir, dataset_name, product_name, start_date, end_date ] if is_met_dataset_downloaded(*args): reply = QMessageBox.question(self.iface.mainWindow( ), 'Existing dataset', ( 'You already downloaded data with the selected dataset/product/date/time combination. ' 'If you continue, this data will be removed.\n' 'Location: {}'.format(get_met_dataset_path(*args))), QMessageBox.Ok, QMessageBox.Cancel) if reply == QMessageBox.Cancel: return lat_north = self.top.value() lat_south = self.bottom.value() lon_west = self.left.value() lon_east = self.right.value() auth = (self.options.rda_username, self.options.rda_password) thread = TaskThread(lambda: download_met_dataset( self.options.met_dir, auth, dataset_name, product_name, param_names, start_date, end_date, lat_south, lat_north, lon_west, lon_east), yields_progress=True) thread.started.connect(self.on_started_download) thread.progress.connect(self.on_progress_download) thread.finished.connect(self.on_finished_download) thread.succeeded.connect(self.on_successful_download) thread.failed.connect(reraise) thread.start()
def run_program_in_background(self, path: str, cwd: str, on_done: Callable[ [str, Union[bool, str, None]], None], supports_mpi: bool) -> None: self.stdout_textarea.clear() # WRF/WPS does not use exit codes to indicate success/failure, # therefore in addition we look for a pattern in the program output. # TODO add 'FATAL' as patterm wrf_error_pattern = 'ERROR' use_mpi = supports_mpi and self.options.mpi_enabled if use_mpi and '-nompi' in path: raise UserError( 'MPI is enabled but your WRF/WPS distribution does not support it.\n' 'In the plugin options, either choose/download a distribution with MPI support or disable MPI' ) # Using QThread and signals (instead of a plain Python thread) is necessary # so that the on_done callback is run on the UI thread, instead of the worker thread. thread = ProgramThread(path, cwd, wrf_error_pattern, use_mpi=use_mpi, mpi_processes=self.options.mpi_processes) def on_output(out: str) -> None: self.stdout_textarea.appendPlainText(out) vert_scrollbar = self.stdout_textarea.verticalScrollBar() vert_scrollbar.setValue(vert_scrollbar.maximum()) def on_finished() -> None: # When lines come in fast, then the highlighter is not called on each line. # Re-highlighting at the end is a work-around to at least have correct # highlighting after program termination. self.stdout_highlighter.rehighlight() if thread.exc_info: on_done(path, None) raise thread.exc_info[0].with_traceback(*thread.exc_info[1:]) on_done(path, thread.error) thread.output.connect(on_output) thread.finished.connect(on_finished) thread.start() # so that we can kill the program later if requested self.thread = thread
def on_ok_clicked(self) -> None: if not self.paths: raise UserError('No GRIB files were added') if not self.interval_input.is_valid(): raise UserError('Interval must be an integer above 0') if self.start_date == self.end_date: raise UserError('Start date cannot be the same as end date') if self.start_date > self.end_date: raise UserError('Start date cannot be after the end date') if not self.vtable_path: raise UserError('No VTable file selected') if not os.path.exists(os.path.join(self.vtable_dir, self.vtable_path)): raise UserError('VTable file does not exist') self.accept()
def create_domain_crs(self) -> CRS: proj = self.get_proj_kwargs() if proj is None: raise UserError('Incomplete projection definition') origin_valid = all( map(lambda w: w.is_valid(), [self.center_lat, self.center_lon])) if origin_valid: origin = LonLat(self.center_lon.value(), self.center_lat.value()) else: origin = LonLat(0, 0) if proj['map_proj'] == 'lambert': crs = CRS.create_lambert(proj['truelat1'], proj['truelat2'], origin) elif proj['map_proj'] == 'lat-lon': crs = CRS.create_lonlat() else: assert False, 'unknown proj: ' + proj['map_proj'] return crs
def kill_program(self): if self.pid == -1: raise UserError('Program not started yet') os.kill(self.pid, signal.SIGTERM)