def send_to_messagebar(self, message, title='', level=Qgis.Info, duration=5, exc_info=None, core_QGIS=False, addToLog=False, showLogPanel=False): """ Add a message to the forms message bar. Args: message (str): Message to display title (str): Title of message. Will appear in bold. Defaults to '' level (QgsMessageBarLevel): The level of message to log. Defaults to Qgis.Info duration (int): Number of seconds to display message for. 0 is no timeout. Defaults to 5 core_QGIS (bool): Add to QGIS interface rather than the dialog addToLog (bool): Also add message to Log. Defaults to False showLogPanel (bool): Display the log panel exc_info () : Information to be used as a traceback if required """ if core_QGIS: newMessageBar = self.iface.messageBar() else: newMessageBar = QgsMessageBar(self) widget = newMessageBar.createMessage(title, message) if showLogPanel: """check out C:\data\GIS_Tools\Reference_Code\QGIS_Reference_Plugins\QGIS-master\python\plugins\db_manager\db_tree.py to try and hyperlink""" button = QPushButton(widget) button.setText('View') button.setContentsMargins(0, 0, 0, 0) button.setFixedWidth(35) button.pressed.connect(openLogPanel) widget.layout().addWidget(button) newMessageBar.pushWidget(widget, level, duration=duration) if not core_QGIS: rowCount = self.validationLayout.count() self.validationLayout.insertRow(rowCount + 1, newMessageBar) if addToLog: if level == 1: # 'WARNING': LOGGER.warning(message) elif level == 2: # 'CRITICAL': # Add a traceback to log only for bailouts only if exc_info is not None: exc_type, exc_value, exc_traceback = sys.exc_info() mess = str(traceback.format_exc()) message = message + '\n' + mess LOGGER.critical(message) else: # INFO = 0 LOGGER.info(message)
class BackupedImgUploaderWizard(QtGui.QWizard, FORM_CLASS): def __init__(self, iface, settings, parent=None): """Constructor.""" super(BackupedImgUploaderWizard, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.iface = iface self.setupUi(self) # Message bars need to be attached to pages, since the wizard object # does not have a layout. It doesn't work to attach the same bar # object to all pages (it is only shown in the last one). The only way # I could make it work was to create different QgsMessageBar objects, # one per page, but it is very to keep track of those references # along the code. It is messy, there should be a better solution. self.bar0 = QgsMessageBar() self.bar0.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.page(0).layout().addWidget(self.bar0) self.bar1 = QgsMessageBar() self.bar1.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.page(1).layout().addWidget(self.bar1) self.bar2 = QgsMessageBar() self.bar2.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.page(2).layout().addWidget(self.bar2) self.setButtonText(QtGui.QWizard.CustomButton1, self.tr("&Start upload")); self.setOption(QtGui.QWizard.HaveCustomButton1, True); self.settings = settings # Dictionaries to save imagery info (todo: defined as a classes in the future) self.metadata = {} self.reprojected = [] self.licensed = [] # Initialize layers and default settings self.loadLayers() self.loadMetadataSettings() self.loadStorageSettings() self.loadOptionsSettings() # register event handlers """ List of page navigation buttons in QWizard, for reference. Please comment out and implement following functions if necessary.""" #self.button(QWizard.BackButton).clicked.connect(self.previousPage) #self.button(QWizard.NextButton).clicked.connect(self.nextPage) #self.button(QWizard.FinishButton).clicked.connect(self.finishWizard) #self.button(QWizard.CancelButton).clicked.connect(self.cancelWizard) # Imagery connections (wizard page 1) self.layers_tool_button.clicked.connect(self.loadLayers) self.file_tool_button.clicked.connect(self.selectFile) self.add_source_button.clicked.connect(self.addSources) self.remove_source_button.clicked.connect(self.removeSources) self.up_source_button.clicked.connect(self.upSource) self.down_source_button.clicked.connect(self.downSource) # Metadata connections (wizard page 2) self.sense_start_edit.setCalendarPopup(1) self.sense_start_edit.setDisplayFormat('dd.MM.yyyy HH:mm') self.sense_end_edit.setCalendarPopup(1) self.sense_end_edit.setDisplayFormat('dd.MM.yyyy HH:mm') self.default_button.clicked.connect(self.loadMetadataSettings) self.clean_button.clicked.connect(self.cleanMetadataSettings) self.save_button.clicked.connect(self.saveMetadata) # Upload tab connections (wizard page 3) self.storage_combo_box.currentIndexChanged.connect(self.enableSpecify) self.customButtonClicked.connect(self.startUploader) # event handling for wizard page 1 def loadLayers(self): all_layers = self.iface.mapCanvas().layers() for layer in all_layers: if not self.layers_list_widget.findItems(layer.name(),Qt.MatchExactly): item = QListWidgetItem() item.setText(layer.name()) item.setData(Qt.UserRole, layer.dataProvider().dataSourceUri()) self.layers_list_widget.addItem(item) def selectFile(self): selected_file = QFileDialog.getOpenFileName( self, 'Select imagery file', os.path.expanduser("~")) self.source_file_edit.setText(selected_file) def addSources(self): filename = self.source_file_edit.text() selected_layers = self.layers_list_widget.selectedItems() if not filename and not selected_layers: self.bar0.clearWidgets() self.bar0.pushMessage( 'WARNING', 'Either a layer or file should be selected to be added', level=QgsMessageBar.WARNING) else: added = False if filename: if self.validateFile(filename): if not self.sources_list_widget.findItems(filename,Qt.MatchExactly): item = QListWidgetItem() item.setText(os.path.basename(filename)) item.setData(Qt.UserRole, filename) self.sources_list_widget.addItem(item) self.added_sources_list_widget.addItem(item.clone()) self.source_file_edit.setText('') added = True if selected_layers: for item in selected_layers: if self.validateLayer(item.text()): if not self.sources_list_widget.findItems(item.text(),Qt.MatchExactly): self.layers_list_widget.takeItem(self.layers_list_widget.row(item)) self.sources_list_widget.addItem(item) self.added_sources_list_widget.addItem(item.clone()) added = True if added: self.bar0.clearWidgets() self.bar0.pushMessage( 'INFO', 'Source(s) added to the upload queue', level=QgsMessageBar.INFO) self.loadMetadataReviewBox() def removeSources(self): selected_sources = self.sources_list_widget.selectedItems() added_sources = self.added_sources_list_widget.selectedItems() if selected_sources: for item in selected_sources: self.sources_list_widget.takeItem(self.sources_list_widget.row(item)) added_item = self.added_sources_list_widget.findItems(item.text(),Qt.MatchExactly) if added_item: self.added_sources_list_widget.takeItem(self.added_sources_list_widget.row(added_item[0])) all_layers = self.iface.mapCanvas().layers() for layer in all_layers: if item.text() == layer.name(): if not self.layers_list_widget.findItems(item.text(),Qt.MatchExactly): self.layers_list_widget.addItem(item) else: self.bar0.clearWidgets() self.bar0.pushMessage( 'WARNING', 'An imagery source must be selected to be removed', level=QgsMessageBar.WARNING) def upSource(self): selected_layers = self.sources_list_widget.selectedItems() if selected_layers: position = self.sources_list_widget.row(selected_layers[0]) if position > 0: item = self.sources_list_widget.takeItem(position) self.sources_list_widget.insertItem(position-1,item) item.setSelected(1) def downSource(self): selected_layers = self.sources_list_widget.selectedItems() if selected_layers: position = self.sources_list_widget.row(selected_layers[0]) if position < self.sources_list_widget.count()-1: item = self.sources_list_widget.takeItem(position) self.sources_list_widget.insertItem(position+1,item) item.setSelected(1) # event handling for wizard page 2 def loadMetadataSettings(self): self.settings.beginGroup("Metadata") self.title_edit.setText(self.settings.value('TITLE')) if self.settings.value('PLATFORM') == None: self.platform_combo_box.setCurrentIndex(0) else: self.platform_combo_box.setCurrentIndex(int(self.settings.value('PLATFORM'))) self.sensor_edit.setText(self.settings.value('SENSOR')) self.sensor_edit.setCursorPosition(0) """ self.sense_start_edit.setDateTime(QDateTime(self.settings.value('SENSE_START'))) self.sense_end_edit.setDateTime(QDateTime(self.settings.value('SENSE_END'))) """ self.sense_start_edit.setDate(QDateTime.fromString( self.settings.value('SENSE_START'), Qt.ISODate).date()) self.sense_start_edit.setTime( QDateTime.fromString(self.settings.value('SENSE_START'), Qt.ISODate).time()) self.sense_end_edit.setDate( QDateTime.fromString(self.settings.value('SENSE_END'), Qt.ISODate).date()) self.sense_end_edit.setTime( QDateTime.fromString(self.settings.value('SENSE_END'), Qt.ISODate).time()) self.tags_edit.setText(self.settings.value('TAGS')) self.tags_edit.setCursorPosition(0) self.provider_edit.setText(self.settings.value('PROVIDER')) self.provider_edit.setCursorPosition(0) self.contact_edit.setText(self.settings.value('CONTACT')) self.contact_edit.setCursorPosition(0) self.website_edit.setText(self.settings.value('WEBSITE')) self.website_edit.setCursorPosition(0) """ Boolean values are converted into string and lower case for 'if' statement, since PyQt sometimes returns 'true', just like C++, instead of 'True', Python style. Maybe we can use integer values (0 or 1), instead of using string. """ if str(self.settings.value('LICENSE')).lower() == 'true': self.license_check_box.setCheckState(2) if str(self.settings.value('REPROJECT')).lower() == 'true': self.reproject_check_box.setCheckState(2) self.settings.endGroup() def cleanMetadataSettings(self): self.title_edit.setText('') self.platform_combo_box.setCurrentIndex(0) self.sensor_edit.setText('') self.sense_start_edit.setDate( QDateTime().fromString('1970-01-01T00:00:00', Qt.ISODate).date()) self.sense_start_edit.setTime( QDateTime().fromString('1970-01-01T00:00:00', Qt.ISODate).time()) self.sense_end_edit.setDate( QDateTime().fromString('1970-01-01T00:00:00',Qt.ISODate).date()) self.sense_end_edit.setTime( QDateTime().fromString('1970-01-01T00:00:00',Qt.ISODate).time()) self.tags_edit.setText('') self.provider_edit.setText('') self.contact_edit.setText('') self.website_edit.setText('') self.license_check_box.setCheckState(0) self.reproject_check_box.setCheckState(0) def saveMetadata(self): selected_layers = self.added_sources_list_widget.selectedItems() if selected_layers: for item in selected_layers: filename = item.data(Qt.UserRole) self.loadImageryInfo(filename) json_string = json.dumps(self.metadata[filename],indent=4,separators=(',', ': ')) if filename not in self.reprojected: json_filename = os.path.splitext(filename)[0]+'.json' else: # to avoid repetition of "EPSG3857" in filename: if not "EPSG3857" in filename: json_filename = os.path.splitext(filename)[0]+'_EPSG3857.json' json_file = open(json_filename, 'w') print >> json_file, json_string json_file.close() self.loadMetadataReviewBox() self.bar1.clearWidgets() self.bar1.pushMessage( 'INFO', 'Metadata for the selected sources were saved', level=QgsMessageBar.INFO) else: self.bar1.clearWidgets() self.bar1.pushMessage( 'WARNING', 'One or more source imagery should be selected to have the metadata saved', level=QgsMessageBar.WARNING) # event handling for wizard page 3 # also see multi-thread for startUploader function def enableSpecify(self): if self.storage_combo_box.currentIndex() == 1: self.specify_label.setEnabled(1) self.specify_edit.setEnabled(1) else: self.specify_label.setEnabled(0) self.specify_edit.setText('') self.specify_edit.setEnabled(0) def loadStorageSettings(self): self.settings.beginGroup("Storage") bucket = self.settings.value('S3_BUCKET_NAME') storage_index = self.storage_combo_box.findText(bucket,Qt.MatchExactly) if not storage_index == -1: self.storage_combo_box.setCurrentIndex(storage_index) else: self.storage_combo_box.setCurrentIndex(self.storage_combo_box.findText(self.tr('other...'))) self.specify_label.setEnabled(1) self.specify_edit.setEnabled(1) self.specify_edit.setText(self.settings.value('S3_BUCKET_NAME')) self.key_id_edit.setText(self.settings.value('AWS_ACCESS_KEY_ID')) self.key_id_edit.setCursorPosition(0) self.secret_key_edit.setText(self.settings.value('AWS_SECRET_ACCESS_KEY')) self.secret_key_edit.setCursorPosition(0) self.settings.endGroup() def loadOptionsSettings(self): self.settings.beginGroup("Options") """ Boolean values are converted into string and lower case for 'if' statement, since PyQt sometimes returns 'true', just like C++, instead of 'True', Python style. Maybe we can use integer values (0 or 1), instead of using string. """ if str(self.settings.value('NOTIFY_OAM')).lower() == 'true': self.notify_oam_check.setCheckState(2) if str(self.settings.value('TRIGGER_OAM_TS')).lower() == 'true': self.trigger_tiling_check.setCheckState(2) self.settings.endGroup() # other functions def validateFile(self,filename): # check that file exists if not os.path.exists(filename): self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "The file %s does not exist" % filename, level=QgsMessageBar.CRITICAL) return False # check that file is an image if imghdr.what(filename) is None: self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "The file %s is not a supported data source" % filename, level=QgsMessageBar.CRITICAL) return False # check if gdal can read file try: raster = gdal.Open(filename,gdal.GA_ReadOnly) except: self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "GDAL could not read file %s" % filename, level=QgsMessageBar.CRITICAL) return False # check if there is an object raster if not raster: self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "GDAL could not read file %s" % filename, level=QgsMessageBar.CRITICAL) return False # check that image has at least 3 bands if raster.RasterCount < 3: self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "The file %s has less than 3 raster bands" % filename, level=QgsMessageBar.CRITICAL) return False # check if projection is set if raster.GetProjection() is '': self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "Could not extract projection from file %s" % filename, level=QgsMessageBar.CRITICAL) return False # finally, check if bbox has valid data xy_points = [(0.0,0.0),(0.0,raster.RasterYSize),(raster.RasterXSize,0.0),(raster.RasterXSize,raster.RasterYSize)] for point in xy_points: if point != self.GDALInfoReportCorner(raster,point[0],point[1]): QgsMessageLog.logMessage( 'File %s is a valid data source' % filename, 'OAM', level=QgsMessageLog.INFO) return True def validateLayer(self,layer_name): all_layers = self.iface.mapCanvas().layers() for layer in all_layers: if layer_name == layer.name(): if layer.type() == QgsMapLayer.VectorLayer: self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "Vector layers cannot be selected for upload", level=QgsMessageBar.CRITICAL) return 0 else: return 1 def extractMetadata(self,filename): """Extract filesize, projection, bbox and gsd from image file""" self.metadata[filename]['File size'] = os.stat(filename).st_size datafile = gdal.Open(filename,gdal.GA_ReadOnly) if datafile is None: self.bar1.clearWidgets() self.bar1.pushMessage( 'CRITICAL', 'Extraction of raster metadata failed', level=QgsMessageBar.CRITICAL) QgsMessageLog.logMessage( 'Failed to extract metadata', 'OAM', level=QgsMessageLog.CRITICAL) else: # extract projection projInfo = datafile.GetProjection() # WKT format spatialRef = osr.SpatialReference() spatialRef.ImportFromWkt(projInfo) # Proj4 format spatialRefProj = spatialRef.ExportToProj4() self.metadata[filename]['Projection'] = str(spatialRefProj) # original bbox upper_left = self.GDALInfoReportCorner(datafile,0.0,0.0 ); lower_left = self.GDALInfoReportCorner(datafile,0.0,datafile.RasterYSize); upper_right = self.GDALInfoReportCorner(datafile,datafile.RasterXSize,0.0 ); lower_right = self.GDALInfoReportCorner(datafile,datafile.RasterXSize,datafile.RasterYSize ); center = self.GDALInfoReportCorner(datafile,datafile.RasterXSize/2.0,datafile.RasterYSize/2.0 ); # get new bbox values if reprojection will happen try: if filename in self.reprojected: self.metadata[filename]['Projection'] = "EPSG:3857" target = osr.SpatialReference() target.ImportFromEPSG(3857) transform = osr.CoordinateTransformation(spatialRef,target) point = ogr.CreateGeometryFromWkt("POINT (%f %f)" % (upper_left[0],upper_left[1])) point.Transform(transform) upper_left = json.loads(point.ExportToJson())['coordinates'] point = ogr.CreateGeometryFromWkt("POINT (%f %f)" % (lower_left[0],lower_left[1])) point.Transform(transform) lower_left = json.loads(point.ExportToJson())['coordinates'] point = ogr.CreateGeometryFromWkt("POINT (%f %f)" % (upper_right[0],upper_right[1])) point.Transform(transform) upper_right = json.loads(point.ExportToJson())['coordinates'] point = ogr.CreateGeometryFromWkt("POINT (%f %f)" % (lower_right[0],lower_right[1])) point.Transform(transform) lower_right = json.loads(point.ExportToJson())['coordinates'] except (RuntimeError, TypeError, NameError) as error: print error except: print "Unexpected error:", sys.exc_info()[0] self.metadata[filename]['BBOX'] = (upper_left,lower_left,upper_right,lower_right) def GDALInfoReportCorner(self,hDataset,x,y): """GDALInfoReportCorner: extracted and adapted from the python port of gdalinfo""" # Transform the point into georeferenced coordinates adfGeoTransform = hDataset.GetGeoTransform(can_return_null = True) if adfGeoTransform is not None: dfGeoX = adfGeoTransform[0] + adfGeoTransform[1] * x + adfGeoTransform[2] * y dfGeoY = adfGeoTransform[3] + adfGeoTransform[4] * x + adfGeoTransform[5] * y else: self.bar1.clearWidgets() self.bar1.pushMessage( 'WARNING', 'BBOX might be wrong. Transformation coefficient could not be fetched from raster', level=QgsMessageBar.WARNING) return (x,y) # Report the georeferenced coordinates if abs(dfGeoX) < 181 and abs(dfGeoY) < 91: return literal_eval(("(%12.7f,%12.7f) " % (dfGeoX, dfGeoY ))) else: return literal_eval(("(%12.3f,%12.3f) " % (dfGeoX, dfGeoY ))) def loadImageryInfoForm(self, filename): pass def loadImageryInfo(self, filename): self.metadata[filename] = {} self.metadata[filename]['Title'] = self.title_edit.text() self.metadata[filename]['Platform'] = self.platform_combo_box.currentIndex() self.metadata[filename]['Sensor'] = self.sensor_edit.text() start = QDateTime() start.setDate(self.sense_start_edit.date()) start.setTime(self.sense_start_edit.time()) self.metadata[filename]['Sensor start'] = start.toString(Qt.ISODate) end = QDateTime() end.setDate(self.sense_end_edit.date()) end.setTime(self.sense_end_edit.time()) self.metadata[filename]['Sensor end'] = end.toString(Qt.ISODate) self.metadata[filename]['Tags'] = self.tags_edit.text() self.metadata[filename]['Provider'] = self.provider_edit.text() self.metadata[filename]['Contact'] = self.contact_edit.text() self.metadata[filename]['Website'] = self.website_edit.text() if self.reproject_check_box.isChecked(): if filename not in self.reprojected: self.reprojected.append(filename) else: while filename in self.reprojected: self.reprojected.remove(filename) if self.license_check_box.isChecked(): self.metadata[filename]['License'] = "Licensed under CC-BY 4.0 and allow tracing in OSM" if filename not in self.licensed: self.licensed.append(filename) else: while filename in self.licensed: self.licensed.remove(filename) self.extractMetadata(filename) def loadMetadataReviewBox(self): json_filenames = [] for index in xrange(self.sources_list_widget.count()): filename = str(self.sources_list_widget.item(index).data(Qt.UserRole)) if filename not in self.reprojected: f = os.path.splitext(filename)[0]+'.json' else: f = os.path.splitext(filename)[0]+'_EPSG3857.json' json_filenames.append(f) temp = QTemporaryFile() temp.open() for f in json_filenames: if os.path.exists(f): with open(f) as infile: temp.write(infile.read()) temp.flush() temp.seek(0) stream = QTextStream(temp) self.review_metadata_box.setText(stream.readAll()) #functions for threading purpose def startConnection(self): if self.storage_combo_box.currentIndex() == 0: bucket_name = 'oam-qgis-plugin-test' else: bucket_name = str(self.specify_edit.text()) if not bucket_name: self.bar2.clearWidgets() self.bar2.pushMessage( 'WARNING', 'The bucket for upload must be provided', level=QgsMessageBar.WARNING) bucket_key = str(self.key_id_edit.text()) bucket_secret = str(self.secret_key_edit.text()) self.bucket = None for trial in xrange(3): if self.bucket: break try: connection = S3Connection(bucket_key,bucket_secret) self.bucket = connection.get_bucket(bucket_name) QgsMessageLog.logMessage( 'Connection established' % trial, 'OAM', level=QgsMessageLog.INFO) except: if trial == 2: QgsMessageLog.logMessage( 'Failed to connect after 3 attempts', 'OAM', level=QgsMessageLog.CRITICAL) return self.bucket def reproject(self,filename): # to avoid repetition of "EPSG3857" in filename: if not "EPSG3857" in filename: reproject_filename = os.path.splitext(filename)[0]+'_EPSG3857.tif' os.system("gdalwarp -of GTiff -t_srs epsg:3857 %s %s" % (filename,reproject_filename)) QgsMessageLog.logMessage( 'Reprojected to EPSG:3857', 'OAM', level=QgsMessageLog.INFO) return reproject_filename def convert(self,filename): tif_filename = os.path.splitext(filename)[0]+".tif" #Open existing dataset src_ds = gdal.Open(filename) driver = gdal.GetDriverByName("GTiff") dst_ds = driver.CreateCopy(tif_filename, src_ds, 0 ) #Properly close the datasets to flush to disk dst_ds = None src_ds = None def startUploader(self): # initialize options self.upload_options = [] if self.notify_oam_check.isChecked(): self.upload_options.append("notify_oam") if self.trigger_tiling_check.isChecked(): self.upload_options.append("trigger_tiling") if self.startConnection(): for index in xrange(self.sources_list_widget.count()): filename = str(self.sources_list_widget.item(index).data(Qt.UserRole)) self.bar2.clearWidgets() self.bar2.pushMessage( 'INFO', 'Pre-upload image processing...', level=QgsMessageBar.INFO) # Perfom reprojection if filename in self.reprojected: filename = self.reproject(filename) QgsMessageLog.logMessage( 'Created reprojected file: %s' % filename, 'OAM', level=QgsMessageLog.INFO) # Convert file format if not (imghdr.what(filename) == 'tiff'): filename = self.convert(filename) QgsMessageLog.logMessage( 'Converted file to tiff: %s' % filename, 'OAM', level=QgsMessageLog.INFO) # create a new uploader instance uploader = Uploader(filename,self.bucket,self.upload_options) QgsMessageLog.logMessage( 'Uploader started\n', 'OAM', level=QgsMessageLog.INFO) # configure the QgsMessageBar messageBar = self.bar2.createMessage('INFO: Performing upload...', ) progressBar = QProgressBar() progressBar.setAlignment(Qt.AlignLeft|Qt.AlignVCenter) messageBar.layout().addWidget(progressBar) cancelButton = QPushButton() cancelButton.setText('Cancel') cancelButton.clicked.connect(self.cancelUpload) messageBar.layout().addWidget(cancelButton) self.bar2.clearWidgets() self.bar2.pushWidget(messageBar, level=QgsMessageBar.INFO) self.messageBar = messageBar # start the worker in a new thread thread = QThread(self) uploader.moveToThread(thread) uploader.finished.connect(self.uploaderFinished) uploader.error.connect(self.uploaderError) uploader.progress.connect(progressBar.setValue) thread.started.connect(uploader.run) thread.start() self.thread = thread self.uploader = uploader else: QgsMessageLog.logMessage( 'No connection to the server\n', 'OAM', level=QgsMessageLog.CRITICAL) def cancelUpload(self): self.uploader.kill() self.bar2.clearWidgets() self.bar2.pushMessage( 'WARNING', 'Canceling upload...', level=QgsMessageBar.WARNING) def uploaderFinished(self, success): # clean up the uploader and thread try: self.uploader.deleteLater() except: QgsMessageLog.logMessage( 'Exception on deleting uploader\n', 'OAM', level=QgsMessageLog.CRITICAL) self.thread.quit() self.thread.wait() try: self.thread.deleteLater() except: QgsMessageLog.logMessage( 'Exception on deleting thread\n', 'OAM', level=QgsMessageLog.CRITICAL) # remove widget from message bar self.bar2.popWidget(self.messageBar) if success: # report the result self.bar2.clearWidgets() self.bar2.pushMessage( 'INFO', 'Upload completed with success', level=QgsMessageBar.INFO) QgsMessageLog.logMessage( 'Upload succeeded', 'OAM', level=QgsMessageLog.INFO) else: # notify the user that something went wrong self.bar2.pushMessage( 'CRITICAL', 'Upload was interrupted', level=QgsMessageBar.CRITICAL) QgsMessageLog.logMessage( 'Upload was interrupted', 'OAM', level=QgsMessageLog.CRITICAL) def uploaderError(self, e, exception_string): QgsMessageLog.logMessage( 'Uploader thread raised an exception:\n'.format(exception_string), 'OAM', level=QgsMessageLog.CRITICAL)
class TrackProfile2webDialog(QtWidgets.QDialog, FORM_CLASS): def __init__(self, parent=None): """Constructor.""" super(TrackProfile2webDialog, self).__init__(parent) # Set up the user interface from Designer through FORM_CLASS. # After self.setupUi() you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.setupUi(self) # filter only QgsVector Line Layers self.layer_combo.setFilters(QgsMapLayerProxyModel.LineLayer) # get QWebView settings and add options for optimization mapview_settings = self.mapview.settings() mapview_settings.setAttribute(QWebSettings.WebGLEnabled, True) mapview_settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) mapview_settings.setAttribute(QWebSettings.Accelerated2dCanvasEnabled, True) self.tile_maps = OrderedDict([ ('OpenTopoMap', { 'tile': 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', 'attribution': 'Map data: © <a href="http://www.openstreetmap.org/copyright">OpenTopoMap</a>, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)' }), ('OpenStreetMap', { 'tile': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 'attribution': 'Map data: © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://openstreetmap.org">OpenStreetMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)' }), ('WikimediaMap', { 'tile': 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png', 'attribution': 'Map data: © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://openstreetmap.org">OpenStreetMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)' }), ('Google', { 'tile': 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', 'attribution': '' }), ('ESRI Satellite Map', { 'tile': 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 'attribution': '© <a href="http://www.esri.com/">Esri</a>' }) ]) self.basemap_combo.clear() for k, v in self.tile_maps.items(): self.basemap_combo.addItem(k, v) # list of attached profile positions as OrderedDict self.profile_position = OrderedDict([ (self.tr('Top Right'), 'topright'), (self.tr('Top Left'), 'topleft'), (self.tr('Bottom Right'), 'bottomright'), (self.tr('Bottom Left'), 'bottomleft') ]) self.profile_combo.clear() for k, v in self.profile_position.items(): self.profile_combo.addItem(k, v) # set default colors self.profile_color.setColor(QColor('#d45f5f')) self.marker_color.setColor(QColor('#ffb800')) self.line_color.setColor(QColor('#ff0000')) # create instance of QgsMessageBar (used later) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.setLayout(QGridLayout()) self.layout().addWidget(self.bar, 1, 0, 1, 1) # connect buttons to specific functions self.update_btn.clicked.connect(self.changeMap) self.browser_btn.clicked.connect(self.openBrowser) self.export_btn.clicked.connect(self.saveAsHtml) self.export_geojson_btn.clicked.connect(self.saveAsHtmlGeojson) # connect to function to disable profile position buttons self.detach_check.toggled.connect(self.refreshState) self.update_btn.clicked.connect(self.refreshState2) # set profile postion buttons as disabled self.label_position.setEnabled(False) self.profile_combo.setEnabled(False) self.auto_hide.setEnabled(False) def refreshState(self): ''' When Detached profile checkbox is checked enable buttons accordingly ''' if not self.detach_check.isChecked(): self.label_position.setEnabled(True) self.profile_combo.setEnabled(True) self.auto_hide.setEnabled(True) else: self.label_position.setEnabled(False) self.profile_combo.setEnabled(False) self.auto_hide.setEnabled(False) def refreshState2(self): ''' Prevent to have clickable button if the Update Map is not clicked at least once ''' self.browser_btn.setEnabled(True) self.export_btn.setEnabled(True) self.export_geojson_btn.setEnabled(True) def changeMap(self): ''' Main function of the plugin. Draws the map with the Line layer and tweaks all the options chosen ''' # reference of the main QgsVectorLayer self.vlayer = self.layer_combo.currentLayer() # Check if the layer as Z values, if not stop the algorithm if not QgsWkbTypes.hasZ(self.vlayer.wkbType()): def open_drape(): ''' function to open Processing algorithm dialog, used later ''' processing.execAlgorithmDialog('native:setzfromraster') # create the widget with the message that will be shown widget = self.bar.createMessage( self. tr("The selected layer has not Z values. Add them using the Drape algorithm of the Processing toolbox" )) # create the button to put into the messageBar button = QPushButton(widget) button.setText(self.tr('Open Drape Algorithm?')) # connect the button to the function to open the drape algorithm button.pressed.connect(open_drape) # add the button to the messageBar widget widget.layout().addWidget(button) # create the messageBar with the message and the button self.bar.pushWidget(widget, Qgis.Critical, duration=10) return # dictionary of all the js functions, will be json dumped after self.opts = { "map": { "center": [41.4583, 12.7059], "zoom": 5, "markerZoomAnimation": False, "zoomControl": False, }, "zoomControl": { "position": 'topleft', }, "elevationControl": { "data": None, #sample data placeholder "options": { "position": 'topright', "theme": 'custom-theme', "useHeightIndicator": True, "collapsed": True, "detachedView": False, "elevationDiv": '#elevation-div', "followPositionMarker": False, "autohide": False, }, }, "layersControl": { "options": { "collapsed": True, }, }, "otmLayer": { "url": None, "options": { "attribution": '', } }, } data = '''{{"name":"{layer}", "type":"FeatureCollection","features": ['''.format( layer=self.vlayer.name()) # set the QgsCoordinateTransform context used in to reproject the layer if needed crsSrc = self.vlayer.crs() crsDest = QgsCoordinateReferenceSystem(4326) xform = QgsCoordinateTransform(crsSrc, crsDest, QgsProject.instance()) # start the loop to write the data formatted in the correct way for i in self.vlayer.getFeatures(): geometry = i.geometry() # transform the geometry to 4326 if needed if not self.vlayer.crs().authid() == 'EPSG:4326': geometry.transform(xform) i.setGeometry(geometry) data += '{ "type": "Feature", "properties": { }, "geometry": { "type": "MultiLineString", "coordinates": [ [' # check and tweak the layer if it is MultiPart and has Multiparts if geometry.isMultipart( ) and i.geometry().constGet().partCount() > 1: g = geometry.asGeometryCollection() for geom in g: for part in geom.constParts(): for coord in part: data += ' [ {}, {},{} ],'.format( coord.x(), coord.y(), coord.z()) # if the layer is not MultiPart continue else: g = geometry.constParts() for part in g: for coord in part.vertices(): data += ' [ {}, {},{} ],'.format( coord.x(), coord.y(), coord.z()) data = data[:-1] data += ']] } },' data = data[:-1] data += ''']}''' # set the user options to the dictionary self.opts["elevationControl"]["data"] = data self.opts["elevationControl"]["options"][ "detachedView"] = self.detach_check.isChecked() self.opts["elevationControl"]["options"][ "followPositionMarker"] = self.follow_track.isChecked() self.opts["elevationControl"]["options"][ "position"] = self.profile_position[ self.profile_combo.currentText()] self.opts["elevationControl"]["options"][ "collapsed"] = self.profile_collapse.isChecked() self.opts["elevationControl"]["options"][ "autohide"] = self.auto_hide.isChecked() self.opts["layersControl"]["options"][ "collapsed"] = self.layers_collapse.isChecked() try: self.opts["otmLayer"]["url"] = self.tile_maps[ self.basemap_combo.currentText()]['tile'] self.opts["otmLayer"]["options"]["attribution"] = self.tile_maps[ self.basemap_combo.currentText()]['attribution'] except KeyError as e: self.opts["otmLayer"]["url"] = self.basemap_combo.currentText() # get the path of the template.html file used to write the final file self.fin = os.path.join(os.path.dirname(__file__), 'template.html') # get the path (from tempfolder) where to write the final result self.fout = os.path.join(tempfile.gettempdir(), 'template.html') # read the content of the input template.html file with open(self.fin, 'r') as fi: self.lines = fi.readlines() for i, j in enumerate(self.lines): if '{ py_opts }' in j: # search for '{{ py_opts }}' placeholder self.dataidx = i if '{ area_fill_color }' in j: # search for '{ area_fill_color }' placeholder area_fill_idx = i if '{ area_stroke_width }' in j: # search for '{ area_stroke_width }' placeholder area_stroke_width_idx = i if '{ marker_color }' in j: # search for '{ marker_color }' placeholder marker_color_idx = i if '{ line_color }' in j: # search for '{ line_color }' placeholder line_color_idx = i if '{ line_opacity }' in j: # search for '{ line_opacity }' placeholder line_opacity_idx = i # dump the opts dictionary as json json_opts = json.dumps(self.opts) # replace all the user options to the correct line indexes self.lines[self.dataidx] = ' var opts = {} ;'.format(json_opts) self.lines[area_fill_idx] = self.lines[area_fill_idx].replace( '{ area_fill_color }', self.profile_color.color().name()) self.lines[area_stroke_width_idx] = self.lines[ area_stroke_width_idx].replace( '{ area_stroke_width }', str(self.profile_stroke_width.value())) self.lines[marker_color_idx] = self.lines[marker_color_idx].replace( '{ marker_color }', self.marker_color.color().name()) self.lines[line_color_idx] = self.lines[line_color_idx].replace( '{ line_color }', self.line_color.color().name()) self.lines[line_opacity_idx] = self.lines[line_opacity_idx].replace( '{ line_opacity }', str(self.marker_opacity.value())) # write the final file with open(self.fout, 'w') as fo: fo.writelines(self.lines) # get the path of the output file as QUrl leaftemplate_new = QUrl.fromLocalFile(self.fout) # load the QUrl into the QWebView self.mapview.load(leaftemplate_new) def openBrowser(self): ''' Open the html in Browser ''' webbrowser.open(self.fout) def saveAsHtml(self): ''' Open a dialog and let the user choose the location to write the file. The file is a single html with the injected data as geojson. ''' dial, _ = QFileDialog.getSaveFileName(None, self.tr("Save Elevation file"), "", "*.html") elevation_file = QFileInfo(dial).absoluteFilePath() elevation_file += '.html' try: copyfile(self.fout, elevation_file) self.bar.pushMessage(self.tr("Elevation file succesfully saved"), "", level=Qgis.Info, duration=3) except: self.bar.pushMessage(self.tr("Something went wrong. Try again."), "", level=Qgis.Critical, duration=3) def saveAsHtmlGeojson(self): ''' Open a directory dialog and le the user choose the location where to write the html and the geojson file. ''' export_name = re.sub(r'\s+', '-', self.vlayer.name()) dir_path = QFileDialog.getExistingDirectory( None, self.tr("Select Directory")) html_name = os.path.join(dir_path, '{}.html'.format(export_name)) # remove all spaces from name gjson_name = os.path.join(dir_path, '{}.geojson'.format(export_name)) output = QgsVectorFileWriter.writeAsVectorFormat(layer=self.vlayer, fileEncoding='UTF-8', fileName=gjson_name, driverName='GeoJSON') # change the raw geojson string with the relative file path self.opts["elevationControl"]["data"] = os.path.basename(gjson_name) geojson_opts = json.dumps(self.opts) self.lines[self.dataidx] = ' var opts = {} ;'.format(geojson_opts) # write the final file try: with open(html_name, 'w') as fog: fog.writelines(self.lines) self.bar.pushMessage(self.tr("Elevation file succesfully saved"), "", level=Qgis.Info, duration=3) except: self.bar.pushMessage(self.tr("Something went wrong. Try again."), "", level=Qgis.Critical, duration=3)
class S3Manager(S3Connection): def __init__(self, access_key_id, secret_access_key, bucket_name, filenames, upload_options, wizard_page, parent=None): S3Connection.__init__(self, access_key_id, secret_access_key) self.upload_options = upload_options self.bucket_name = bucket_name self.bucket = None self.filenames = filenames self.s3Uploaders = [] self.threads = [] self.count_uploaded_images = 0 self.num_uploading_images = 0 #For GUI (messages and progress bars) self.wizard_page = wizard_page self.uploader_widget = QtGui.QWidget() self.uploader_widget.setWindowTitle("Upload Progress Bars") self.uploader_widget.setWindowFlags(Qt.WindowStaysOnTopHint) self.uploader_v_box = QtGui.QVBoxLayout() self.uploader_widget.setLayout(self.uploader_v_box) self.msg_bar_main = None self.msg_bar_main_content = None self.cancel_button_main = None self.msg_bars = [] self.msg_bars_content = [] self.progress_bars = [] self.cancel_buttons = [] def getBucket(self): for trial in xrange(3): if self.bucket: break try: self.bucket = super(S3Manager, self).get_bucket(self.bucket_name) QgsMessageLog.logMessage('Connection established' % trial, 'OAM', level=QgsMessageLog.INFO) except: if trial == 2: QgsMessageLog.logMessage( 'Failed to connect after 3 attempts', 'OAM', level=QgsMessageLog.CRITICAL) return self.bucket """for testing purpose""" """ rsKeys = [] for key in self.bucket.list(): rsKeys.append(repr(key)) return rsKeys """ #functions for threading purpose def uploadFiles(self): """ Testing purpose only """ if "notify_oam" in self.upload_options: print "notify_oam" if "trigger_tiling" in self.upload_options: print "trigger_tiling" # configure the msg_bar_main (including Cancel button and its container) self.msg_bar_main = QgsMessageBar() self.msg_bar_main.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.wizard_page.layout().addWidget(self.msg_bar_main) self.msg_bar_main_content = self.msg_bar_main.createMessage( 'Performing upload...', ) self.cancel_button_main = QPushButton() self.cancel_button_main.setText('Cancel') self.cancel_button_main.clicked.connect(self.cancelAllUploads) self.msg_bar_main_content.layout().addWidget(self.cancel_button_main) self.msg_bar_main.clearWidgets() self.msg_bar_main.pushWidget(self.msg_bar_main_content, level=QgsMessageBar.INFO) self.num_uploading_images = len(self.filenames) for i in range(0, self.num_uploading_images): filename = self.filenames[i] # create a new S3Uploader instance self.s3Uploaders.append( S3Uploader(filename, self.bucket, self.upload_options, i)) try: # start the worker in a new thread self.threads.append(QThread()) self.s3Uploaders[i].moveToThread(self.threads[i]) self.s3Uploaders[i].finished.connect(self.finishUpload) self.s3Uploaders[i].error.connect(self.displayUploadError) self.threads[i].started.connect(self.s3Uploaders[i].run) self.threads[i].start() print repr(self.threads[i]) # configure the msg_bars for progress bar self.msg_bars.append(QgsMessageBar()) self.msg_bars[i].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) #self.wizard_page.layout().addWidget(self.msg_bars[i]) self.uploader_v_box.addWidget(self.msg_bars[i]) #set the texts of uploading files file_basename = str(os.path.basename(str(filename))) self.msg_bars_content.append(self.msg_bars[i].createMessage( file_basename, )) self.msg_bars[i].clearWidgets() self.msg_bars[i].pushWidget(self.msg_bars_content[i], level=QgsMessageBar.INFO) #add progress bars in the message bar self.progress_bars.append(QProgressBar()) self.progress_bars[i].setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.msg_bars_content[i].layout().addWidget( self.progress_bars[i]) self.s3Uploaders[i].progress.connect(self.updateProgressBar) #set cancel button in the message bar for each upload file """ self.cancel_buttons.append(QPushButton()) self.cancel_buttons[i].setText('Cancel') self.cancel_buttons[i].clicked.connect(self.cancelAllUploads) self.msg_bars_content[i].layout().addWidget(self.cancel_buttons[i]) #self.cancel_buttons[i].clicked.connect(self.cancelUpload) """ except Exception, e: return repr(e) #Display upload progress bars in a separate widget self.uploader_widget.show() screenShape = QtGui.QDesktopWidget().screenGeometry() width, height = screenShape.width(), screenShape.height() winW, winH = self.uploader_widget.frameGeometry().width( ), self.uploader_widget.frameGeometry().height() left = width - (winW + 10) top = height - (winH + 50) self.uploader_widget.move(left, top) #self.uploader_widget.activateWindow() return True
class BackupedImgUploaderWizard(QtGui.QWizard, FORM_CLASS): def __init__(self, iface, settings, parent=None): """Constructor.""" super(BackupedImgUploaderWizard, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-auto-connect self.iface = iface self.setupUi(self) # Message bars need to be attached to pages, since the wizard object # does not have a layout. It doesn't work to attach the same bar # object to all pages (it is only shown in the last one). The only way # I could make it work was to create different QgsMessageBar objects, # one per page, but it is very to keep track of those references # along the code. It is messy, there should be a better solution. self.bar0 = QgsMessageBar() self.bar0.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.page(0).layout().addWidget(self.bar0) self.bar1 = QgsMessageBar() self.bar1.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.page(1).layout().addWidget(self.bar1) self.bar2 = QgsMessageBar() self.bar2.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.page(2).layout().addWidget(self.bar2) self.setButtonText(QtGui.QWizard.CustomButton1, self.tr("&Start upload")) self.setOption(QtGui.QWizard.HaveCustomButton1, True) self.settings = settings # Dictionaries to save imagery info (todo: defined as a classes in the future) self.metadata = {} self.reprojected = [] self.licensed = [] # Initialize layers and default settings self.loadLayers() self.loadMetadataSettings() self.loadStorageSettings() self.loadOptionsSettings() # register event handlers """ List of page navigation buttons in QWizard, for reference. Please comment out and implement following functions if necessary.""" #self.button(QWizard.BackButton).clicked.connect(self.previousPage) #self.button(QWizard.NextButton).clicked.connect(self.nextPage) #self.button(QWizard.FinishButton).clicked.connect(self.finishWizard) #self.button(QWizard.CancelButton).clicked.connect(self.cancelWizard) # Imagery connections (wizard page 1) self.layers_tool_button.clicked.connect(self.loadLayers) self.file_tool_button.clicked.connect(self.selectFile) self.add_source_button.clicked.connect(self.addSources) self.remove_source_button.clicked.connect(self.removeSources) self.up_source_button.clicked.connect(self.upSource) self.down_source_button.clicked.connect(self.downSource) # Metadata connections (wizard page 2) self.sense_start_edit.setCalendarPopup(1) self.sense_start_edit.setDisplayFormat('dd.MM.yyyy HH:mm') self.sense_end_edit.setCalendarPopup(1) self.sense_end_edit.setDisplayFormat('dd.MM.yyyy HH:mm') self.default_button.clicked.connect(self.loadMetadataSettings) self.clean_button.clicked.connect(self.cleanMetadataSettings) self.save_button.clicked.connect(self.saveMetadata) # Upload tab connections (wizard page 3) self.storage_combo_box.currentIndexChanged.connect(self.enableSpecify) self.customButtonClicked.connect(self.startUploader) # event handling for wizard page 1 def loadLayers(self): all_layers = self.iface.mapCanvas().layers() for layer in all_layers: if not self.layers_list_widget.findItems(layer.name(), Qt.MatchExactly): item = QListWidgetItem() item.setText(layer.name()) item.setData(Qt.UserRole, layer.dataProvider().dataSourceUri()) self.layers_list_widget.addItem(item) def selectFile(self): selected_file = QFileDialog.getOpenFileName(self, 'Select imagery file', os.path.expanduser("~")) self.source_file_edit.setText(selected_file) def addSources(self): filename = self.source_file_edit.text() selected_layers = self.layers_list_widget.selectedItems() if not filename and not selected_layers: self.bar0.clearWidgets() self.bar0.pushMessage( 'WARNING', 'Either a layer or file should be selected to be added', level=QgsMessageBar.WARNING) else: added = False if filename: if self.validateFile(filename): if not self.sources_list_widget.findItems( filename, Qt.MatchExactly): item = QListWidgetItem() item.setText(os.path.basename(filename)) item.setData(Qt.UserRole, filename) self.sources_list_widget.addItem(item) self.added_sources_list_widget.addItem(item.clone()) self.source_file_edit.setText('') added = True if selected_layers: for item in selected_layers: if self.validateLayer(item.text()): if not self.sources_list_widget.findItems( item.text(), Qt.MatchExactly): self.layers_list_widget.takeItem( self.layers_list_widget.row(item)) self.sources_list_widget.addItem(item) self.added_sources_list_widget.addItem( item.clone()) added = True if added: self.bar0.clearWidgets() self.bar0.pushMessage('INFO', 'Source(s) added to the upload queue', level=QgsMessageBar.INFO) self.loadMetadataReviewBox() def removeSources(self): selected_sources = self.sources_list_widget.selectedItems() added_sources = self.added_sources_list_widget.selectedItems() if selected_sources: for item in selected_sources: self.sources_list_widget.takeItem( self.sources_list_widget.row(item)) added_item = self.added_sources_list_widget.findItems( item.text(), Qt.MatchExactly) if added_item: self.added_sources_list_widget.takeItem( self.added_sources_list_widget.row(added_item[0])) all_layers = self.iface.mapCanvas().layers() for layer in all_layers: if item.text() == layer.name(): if not self.layers_list_widget.findItems( item.text(), Qt.MatchExactly): self.layers_list_widget.addItem(item) else: self.bar0.clearWidgets() self.bar0.pushMessage( 'WARNING', 'An imagery source must be selected to be removed', level=QgsMessageBar.WARNING) def upSource(self): selected_layers = self.sources_list_widget.selectedItems() if selected_layers: position = self.sources_list_widget.row(selected_layers[0]) if position > 0: item = self.sources_list_widget.takeItem(position) self.sources_list_widget.insertItem(position - 1, item) item.setSelected(1) def downSource(self): selected_layers = self.sources_list_widget.selectedItems() if selected_layers: position = self.sources_list_widget.row(selected_layers[0]) if position < self.sources_list_widget.count() - 1: item = self.sources_list_widget.takeItem(position) self.sources_list_widget.insertItem(position + 1, item) item.setSelected(1) # event handling for wizard page 2 def loadMetadataSettings(self): self.settings.beginGroup("Metadata") self.title_edit.setText(self.settings.value('TITLE')) if self.settings.value('PLATFORM') == None: self.platform_combo_box.setCurrentIndex(0) else: self.platform_combo_box.setCurrentIndex( int(self.settings.value('PLATFORM'))) self.sensor_edit.setText(self.settings.value('SENSOR')) self.sensor_edit.setCursorPosition(0) """ self.sense_start_edit.setDateTime(QDateTime(self.settings.value('SENSE_START'))) self.sense_end_edit.setDateTime(QDateTime(self.settings.value('SENSE_END'))) """ self.sense_start_edit.setDate( QDateTime.fromString(self.settings.value('SENSE_START'), Qt.ISODate).date()) self.sense_start_edit.setTime( QDateTime.fromString(self.settings.value('SENSE_START'), Qt.ISODate).time()) self.sense_end_edit.setDate( QDateTime.fromString(self.settings.value('SENSE_END'), Qt.ISODate).date()) self.sense_end_edit.setTime( QDateTime.fromString(self.settings.value('SENSE_END'), Qt.ISODate).time()) self.tags_edit.setText(self.settings.value('TAGS')) self.tags_edit.setCursorPosition(0) self.provider_edit.setText(self.settings.value('PROVIDER')) self.provider_edit.setCursorPosition(0) self.contact_edit.setText(self.settings.value('CONTACT')) self.contact_edit.setCursorPosition(0) self.website_edit.setText(self.settings.value('WEBSITE')) self.website_edit.setCursorPosition(0) """ Boolean values are converted into string and lower case for 'if' statement, since PyQt sometimes returns 'true', just like C++, instead of 'True', Python style. Maybe we can use integer values (0 or 1), instead of using string. """ if str(self.settings.value('LICENSE')).lower() == 'true': self.license_check_box.setCheckState(2) if str(self.settings.value('REPROJECT')).lower() == 'true': self.reproject_check_box.setCheckState(2) self.settings.endGroup() def cleanMetadataSettings(self): self.title_edit.setText('') self.platform_combo_box.setCurrentIndex(0) self.sensor_edit.setText('') self.sense_start_edit.setDate(QDateTime().fromString( '1970-01-01T00:00:00', Qt.ISODate).date()) self.sense_start_edit.setTime(QDateTime().fromString( '1970-01-01T00:00:00', Qt.ISODate).time()) self.sense_end_edit.setDate(QDateTime().fromString( '1970-01-01T00:00:00', Qt.ISODate).date()) self.sense_end_edit.setTime(QDateTime().fromString( '1970-01-01T00:00:00', Qt.ISODate).time()) self.tags_edit.setText('') self.provider_edit.setText('') self.contact_edit.setText('') self.website_edit.setText('') self.license_check_box.setCheckState(0) self.reproject_check_box.setCheckState(0) def saveMetadata(self): selected_layers = self.added_sources_list_widget.selectedItems() if selected_layers: for item in selected_layers: filename = item.data(Qt.UserRole) self.loadImageryInfo(filename) json_string = json.dumps(self.metadata[filename], indent=4, separators=(',', ': ')) if filename not in self.reprojected: json_filename = os.path.splitext(filename)[0] + '.json' else: # to avoid repetition of "EPSG3857" in filename: if not "EPSG3857" in filename: json_filename = os.path.splitext( filename)[0] + '_EPSG3857.json' json_file = open(json_filename, 'w') print >> json_file, json_string json_file.close() self.loadMetadataReviewBox() self.bar1.clearWidgets() self.bar1.pushMessage( 'INFO', 'Metadata for the selected sources were saved', level=QgsMessageBar.INFO) else: self.bar1.clearWidgets() self.bar1.pushMessage( 'WARNING', 'One or more source imagery should be selected to have the metadata saved', level=QgsMessageBar.WARNING) # event handling for wizard page 3 # also see multi-thread for startUploader function def enableSpecify(self): if self.storage_combo_box.currentIndex() == 1: self.specify_label.setEnabled(1) self.specify_edit.setEnabled(1) else: self.specify_label.setEnabled(0) self.specify_edit.setText('') self.specify_edit.setEnabled(0) def loadStorageSettings(self): self.settings.beginGroup("Storage") bucket = self.settings.value('S3_BUCKET_NAME') storage_index = self.storage_combo_box.findText( bucket, Qt.MatchExactly) if not storage_index == -1: self.storage_combo_box.setCurrentIndex(storage_index) else: self.storage_combo_box.setCurrentIndex( self.storage_combo_box.findText(self.tr('other...'))) self.specify_label.setEnabled(1) self.specify_edit.setEnabled(1) self.specify_edit.setText(self.settings.value('S3_BUCKET_NAME')) self.key_id_edit.setText(self.settings.value('AWS_ACCESS_KEY_ID')) self.key_id_edit.setCursorPosition(0) self.secret_key_edit.setText( self.settings.value('AWS_SECRET_ACCESS_KEY')) self.secret_key_edit.setCursorPosition(0) self.settings.endGroup() def loadOptionsSettings(self): self.settings.beginGroup("Options") """ Boolean values are converted into string and lower case for 'if' statement, since PyQt sometimes returns 'true', just like C++, instead of 'True', Python style. Maybe we can use integer values (0 or 1), instead of using string. """ if str(self.settings.value('NOTIFY_OAM')).lower() == 'true': self.notify_oam_check.setCheckState(2) if str(self.settings.value('TRIGGER_OAM_TS')).lower() == 'true': self.trigger_tiling_check.setCheckState(2) self.settings.endGroup() # other functions def validateFile(self, filename): # check that file exists if not os.path.exists(filename): self.bar0.clearWidgets() self.bar0.pushMessage("CRITICAL", "The file %s does not exist" % filename, level=QgsMessageBar.CRITICAL) return False # check that file is an image if imghdr.what(filename) is None: self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "The file %s is not a supported data source" % filename, level=QgsMessageBar.CRITICAL) return False # check if gdal can read file try: raster = gdal.Open(filename, gdal.GA_ReadOnly) except: self.bar0.clearWidgets() self.bar0.pushMessage("CRITICAL", "GDAL could not read file %s" % filename, level=QgsMessageBar.CRITICAL) return False # check if there is an object raster if not raster: self.bar0.clearWidgets() self.bar0.pushMessage("CRITICAL", "GDAL could not read file %s" % filename, level=QgsMessageBar.CRITICAL) return False # check that image has at least 3 bands if raster.RasterCount < 3: self.bar0.clearWidgets() self.bar0.pushMessage("CRITICAL", "The file %s has less than 3 raster bands" % filename, level=QgsMessageBar.CRITICAL) return False # check if projection is set if raster.GetProjection() is '': self.bar0.clearWidgets() self.bar0.pushMessage("CRITICAL", "Could not extract projection from file %s" % filename, level=QgsMessageBar.CRITICAL) return False # finally, check if bbox has valid data xy_points = [(0.0, 0.0), (0.0, raster.RasterYSize), (raster.RasterXSize, 0.0), (raster.RasterXSize, raster.RasterYSize)] for point in xy_points: if point != self.GDALInfoReportCorner(raster, point[0], point[1]): QgsMessageLog.logMessage('File %s is a valid data source' % filename, 'OAM', level=QgsMessageLog.INFO) return True def validateLayer(self, layer_name): all_layers = self.iface.mapCanvas().layers() for layer in all_layers: if layer_name == layer.name(): if layer.type() == QgsMapLayer.VectorLayer: self.bar0.clearWidgets() self.bar0.pushMessage( "CRITICAL", "Vector layers cannot be selected for upload", level=QgsMessageBar.CRITICAL) return 0 else: return 1 def extractMetadata(self, filename): """Extract filesize, projection, bbox and gsd from image file""" self.metadata[filename]['File size'] = os.stat(filename).st_size datafile = gdal.Open(filename, gdal.GA_ReadOnly) if datafile is None: self.bar1.clearWidgets() self.bar1.pushMessage('CRITICAL', 'Extraction of raster metadata failed', level=QgsMessageBar.CRITICAL) QgsMessageLog.logMessage('Failed to extract metadata', 'OAM', level=QgsMessageLog.CRITICAL) else: # extract projection projInfo = datafile.GetProjection() # WKT format spatialRef = osr.SpatialReference() spatialRef.ImportFromWkt(projInfo) # Proj4 format spatialRefProj = spatialRef.ExportToProj4() self.metadata[filename]['Projection'] = str(spatialRefProj) # original bbox upper_left = self.GDALInfoReportCorner(datafile, 0.0, 0.0) lower_left = self.GDALInfoReportCorner(datafile, 0.0, datafile.RasterYSize) upper_right = self.GDALInfoReportCorner(datafile, datafile.RasterXSize, 0.0) lower_right = self.GDALInfoReportCorner(datafile, datafile.RasterXSize, datafile.RasterYSize) center = self.GDALInfoReportCorner(datafile, datafile.RasterXSize / 2.0, datafile.RasterYSize / 2.0) # get new bbox values if reprojection will happen try: if filename in self.reprojected: self.metadata[filename]['Projection'] = "EPSG:3857" target = osr.SpatialReference() target.ImportFromEPSG(3857) transform = osr.CoordinateTransformation( spatialRef, target) point = ogr.CreateGeometryFromWkt( "POINT (%f %f)" % (upper_left[0], upper_left[1])) point.Transform(transform) upper_left = json.loads( point.ExportToJson())['coordinates'] point = ogr.CreateGeometryFromWkt( "POINT (%f %f)" % (lower_left[0], lower_left[1])) point.Transform(transform) lower_left = json.loads( point.ExportToJson())['coordinates'] point = ogr.CreateGeometryFromWkt( "POINT (%f %f)" % (upper_right[0], upper_right[1])) point.Transform(transform) upper_right = json.loads( point.ExportToJson())['coordinates'] point = ogr.CreateGeometryFromWkt( "POINT (%f %f)" % (lower_right[0], lower_right[1])) point.Transform(transform) lower_right = json.loads( point.ExportToJson())['coordinates'] except (RuntimeError, TypeError, NameError) as error: print error except: print "Unexpected error:", sys.exc_info()[0] self.metadata[filename]['BBOX'] = (upper_left, lower_left, upper_right, lower_right) def GDALInfoReportCorner(self, hDataset, x, y): """GDALInfoReportCorner: extracted and adapted from the python port of gdalinfo""" # Transform the point into georeferenced coordinates adfGeoTransform = hDataset.GetGeoTransform(can_return_null=True) if adfGeoTransform is not None: dfGeoX = adfGeoTransform[ 0] + adfGeoTransform[1] * x + adfGeoTransform[2] * y dfGeoY = adfGeoTransform[ 3] + adfGeoTransform[4] * x + adfGeoTransform[5] * y else: self.bar1.clearWidgets() self.bar1.pushMessage( 'WARNING', 'BBOX might be wrong. Transformation coefficient could not be fetched from raster', level=QgsMessageBar.WARNING) return (x, y) # Report the georeferenced coordinates if abs(dfGeoX) < 181 and abs(dfGeoY) < 91: return literal_eval(("(%12.7f,%12.7f) " % (dfGeoX, dfGeoY))) else: return literal_eval(("(%12.3f,%12.3f) " % (dfGeoX, dfGeoY))) def loadImageryInfoForm(self, filename): pass def loadImageryInfo(self, filename): self.metadata[filename] = {} self.metadata[filename]['Title'] = self.title_edit.text() self.metadata[filename][ 'Platform'] = self.platform_combo_box.currentIndex() self.metadata[filename]['Sensor'] = self.sensor_edit.text() start = QDateTime() start.setDate(self.sense_start_edit.date()) start.setTime(self.sense_start_edit.time()) self.metadata[filename]['Sensor start'] = start.toString(Qt.ISODate) end = QDateTime() end.setDate(self.sense_end_edit.date()) end.setTime(self.sense_end_edit.time()) self.metadata[filename]['Sensor end'] = end.toString(Qt.ISODate) self.metadata[filename]['Tags'] = self.tags_edit.text() self.metadata[filename]['Provider'] = self.provider_edit.text() self.metadata[filename]['Contact'] = self.contact_edit.text() self.metadata[filename]['Website'] = self.website_edit.text() if self.reproject_check_box.isChecked(): if filename not in self.reprojected: self.reprojected.append(filename) else: while filename in self.reprojected: self.reprojected.remove(filename) if self.license_check_box.isChecked(): self.metadata[filename][ 'License'] = "Licensed under CC-BY 4.0 and allow tracing in OSM" if filename not in self.licensed: self.licensed.append(filename) else: while filename in self.licensed: self.licensed.remove(filename) self.extractMetadata(filename) def loadMetadataReviewBox(self): json_filenames = [] for index in xrange(self.sources_list_widget.count()): filename = str( self.sources_list_widget.item(index).data(Qt.UserRole)) if filename not in self.reprojected: f = os.path.splitext(filename)[0] + '.json' else: f = os.path.splitext(filename)[0] + '_EPSG3857.json' json_filenames.append(f) temp = QTemporaryFile() temp.open() for f in json_filenames: if os.path.exists(f): with open(f) as infile: temp.write(infile.read()) temp.flush() temp.seek(0) stream = QTextStream(temp) self.review_metadata_box.setText(stream.readAll()) #functions for threading purpose def startConnection(self): if self.storage_combo_box.currentIndex() == 0: bucket_name = 'oam-qgis-plugin-test' else: bucket_name = str(self.specify_edit.text()) if not bucket_name: self.bar2.clearWidgets() self.bar2.pushMessage('WARNING', 'The bucket for upload must be provided', level=QgsMessageBar.WARNING) bucket_key = str(self.key_id_edit.text()) bucket_secret = str(self.secret_key_edit.text()) self.bucket = None for trial in xrange(3): if self.bucket: break try: connection = S3Connection(bucket_key, bucket_secret) self.bucket = connection.get_bucket(bucket_name) QgsMessageLog.logMessage('Connection established' % trial, 'OAM', level=QgsMessageLog.INFO) except: if trial == 2: QgsMessageLog.logMessage( 'Failed to connect after 3 attempts', 'OAM', level=QgsMessageLog.CRITICAL) return self.bucket def reproject(self, filename): # to avoid repetition of "EPSG3857" in filename: if not "EPSG3857" in filename: reproject_filename = os.path.splitext( filename)[0] + '_EPSG3857.tif' os.system("gdalwarp -of GTiff -t_srs epsg:3857 %s %s" % (filename, reproject_filename)) QgsMessageLog.logMessage('Reprojected to EPSG:3857', 'OAM', level=QgsMessageLog.INFO) return reproject_filename def convert(self, filename): tif_filename = os.path.splitext(filename)[0] + ".tif" #Open existing dataset src_ds = gdal.Open(filename) driver = gdal.GetDriverByName("GTiff") dst_ds = driver.CreateCopy(tif_filename, src_ds, 0) #Properly close the datasets to flush to disk dst_ds = None src_ds = None def startUploader(self): # initialize options self.upload_options = [] if self.notify_oam_check.isChecked(): self.upload_options.append("notify_oam") if self.trigger_tiling_check.isChecked(): self.upload_options.append("trigger_tiling") if self.startConnection(): for index in xrange(self.sources_list_widget.count()): filename = str( self.sources_list_widget.item(index).data(Qt.UserRole)) self.bar2.clearWidgets() self.bar2.pushMessage('INFO', 'Pre-upload image processing...', level=QgsMessageBar.INFO) # Perfom reprojection if filename in self.reprojected: filename = self.reproject(filename) QgsMessageLog.logMessage('Created reprojected file: %s' % filename, 'OAM', level=QgsMessageLog.INFO) # Convert file format if not (imghdr.what(filename) == 'tiff'): filename = self.convert(filename) QgsMessageLog.logMessage('Converted file to tiff: %s' % filename, 'OAM', level=QgsMessageLog.INFO) # create a new uploader instance uploader = Uploader(filename, self.bucket, self.upload_options) QgsMessageLog.logMessage('Uploader started\n', 'OAM', level=QgsMessageLog.INFO) # configure the QgsMessageBar messageBar = self.bar2.createMessage( 'INFO: Performing upload...', ) progressBar = QProgressBar() progressBar.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) messageBar.layout().addWidget(progressBar) cancelButton = QPushButton() cancelButton.setText('Cancel') cancelButton.clicked.connect(self.cancelUpload) messageBar.layout().addWidget(cancelButton) self.bar2.clearWidgets() self.bar2.pushWidget(messageBar, level=QgsMessageBar.INFO) self.messageBar = messageBar # start the worker in a new thread thread = QThread(self) uploader.moveToThread(thread) uploader.finished.connect(self.uploaderFinished) uploader.error.connect(self.uploaderError) uploader.progress.connect(progressBar.setValue) thread.started.connect(uploader.run) thread.start() self.thread = thread self.uploader = uploader else: QgsMessageLog.logMessage('No connection to the server\n', 'OAM', level=QgsMessageLog.CRITICAL) def cancelUpload(self): self.uploader.kill() self.bar2.clearWidgets() self.bar2.pushMessage('WARNING', 'Canceling upload...', level=QgsMessageBar.WARNING) def uploaderFinished(self, success): # clean up the uploader and thread try: self.uploader.deleteLater() except: QgsMessageLog.logMessage('Exception on deleting uploader\n', 'OAM', level=QgsMessageLog.CRITICAL) self.thread.quit() self.thread.wait() try: self.thread.deleteLater() except: QgsMessageLog.logMessage('Exception on deleting thread\n', 'OAM', level=QgsMessageLog.CRITICAL) # remove widget from message bar self.bar2.popWidget(self.messageBar) if success: # report the result self.bar2.clearWidgets() self.bar2.pushMessage('INFO', 'Upload completed with success', level=QgsMessageBar.INFO) QgsMessageLog.logMessage('Upload succeeded', 'OAM', level=QgsMessageLog.INFO) else: # notify the user that something went wrong self.bar2.pushMessage('CRITICAL', 'Upload was interrupted', level=QgsMessageBar.CRITICAL) QgsMessageLog.logMessage('Upload was interrupted', 'OAM', level=QgsMessageLog.CRITICAL) def uploaderError(self, e, exception_string): QgsMessageLog.logMessage( 'Uploader thread raised an exception:\n'.format(exception_string), 'OAM', level=QgsMessageLog.CRITICAL)
class S3Manager(S3Connection): def __init__(self, access_key_id, secret_access_key, bucket_name, filenames, upload_options, wizard_page, parent=None): S3Connection.__init__(self, access_key_id, secret_access_key) self.upload_options = upload_options self.bucket_name = bucket_name self.bucket = None self.filenames = filenames self.s3Uploaders = [] self.threads = [] self.count_uploaded_images = 0 self.num_uploading_images = 0 #For GUI (messages and progress bars) self.wizard_page = wizard_page self.uploader_widget = QtGui.QWidget() self.uploader_widget.setWindowTitle("Upload Progress Bars") self.uploader_widget.setWindowFlags(Qt.WindowStaysOnTopHint) self.uploader_v_box = QtGui.QVBoxLayout() self.uploader_widget.setLayout(self.uploader_v_box) self.msg_bar_main = None self.msg_bar_main_content = None self.cancel_button_main = None self.msg_bars = [] self.msg_bars_content = [] self.progress_bars = [] self.cancel_buttons = [] def getBucket(self): for trial in xrange(3): if self.bucket: break try: self.bucket = super(S3Manager,self).get_bucket(self.bucket_name) QgsMessageLog.logMessage( 'Connection established' % trial, 'OAM', level=QgsMessageLog.INFO) except: if trial == 2: QgsMessageLog.logMessage( 'Failed to connect after 3 attempts', 'OAM', level=QgsMessageLog.CRITICAL) return self.bucket """for testing purpose""" """ rsKeys = [] for key in self.bucket.list(): rsKeys.append(repr(key)) return rsKeys """ #functions for threading purpose def uploadFiles(self): """ Testing purpose only """ if "notify_oam" in self.upload_options: print "notify_oam" if "trigger_tiling" in self.upload_options: print "trigger_tiling" # configure the msg_bar_main (including Cancel button and its container) self.msg_bar_main = QgsMessageBar() self.msg_bar_main.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.wizard_page.layout().addWidget(self.msg_bar_main) self.msg_bar_main_content = self.msg_bar_main.createMessage('Performing upload...', ) self.cancel_button_main = QPushButton() self.cancel_button_main.setText('Cancel') self.cancel_button_main.clicked.connect(self.cancelAllUploads) self.msg_bar_main_content.layout().addWidget(self.cancel_button_main) self.msg_bar_main.clearWidgets() self.msg_bar_main.pushWidget(self.msg_bar_main_content, level=QgsMessageBar.INFO) self.num_uploading_images = len(self.filenames) for i in range(0, self.num_uploading_images): filename = self.filenames[i] # create a new S3Uploader instance self.s3Uploaders.append(S3Uploader(filename, self.bucket, self.upload_options, i)) try: # start the worker in a new thread self.threads.append(QThread()) self.s3Uploaders[i].moveToThread(self.threads[i]) self.s3Uploaders[i].finished.connect(self.finishUpload) self.s3Uploaders[i].error.connect(self.displayUploadError) self.threads[i].started.connect(self.s3Uploaders[i].run) self.threads[i].start() print repr(self.threads[i]) # configure the msg_bars for progress bar self.msg_bars.append(QgsMessageBar()) self.msg_bars[i].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) #self.wizard_page.layout().addWidget(self.msg_bars[i]) self.uploader_v_box.addWidget(self.msg_bars[i]) #set the texts of uploading files file_basename = str(os.path.basename(str(filename))) self.msg_bars_content.append(self.msg_bars[i].createMessage(file_basename, )) self.msg_bars[i].clearWidgets() self.msg_bars[i].pushWidget(self.msg_bars_content[i], level=QgsMessageBar.INFO) #add progress bars in the message bar self.progress_bars.append(QProgressBar()) self.progress_bars[i].setAlignment(Qt.AlignLeft|Qt.AlignVCenter) self.msg_bars_content[i].layout().addWidget(self.progress_bars[i]) self.s3Uploaders[i].progress.connect(self.updateProgressBar) #set cancel button in the message bar for each upload file """ self.cancel_buttons.append(QPushButton()) self.cancel_buttons[i].setText('Cancel') self.cancel_buttons[i].clicked.connect(self.cancelAllUploads) self.msg_bars_content[i].layout().addWidget(self.cancel_buttons[i]) #self.cancel_buttons[i].clicked.connect(self.cancelUpload) """ except Exception, e: return repr(e) #Display upload progress bars in a separate widget self.uploader_widget.show() screenShape = QtGui.QDesktopWidget().screenGeometry() width, height = screenShape.width(), screenShape.height() winW, winH = self.uploader_widget.frameGeometry().width(), self.uploader_widget.frameGeometry().height() left = width - (winW + 10) top = height - (winH + 50) self.uploader_widget.move(left,top) #self.uploader_widget.activateWindow() return True
class PublishWidget(BASE, WIDGET): def __init__(self, parent): super(PublishWidget, self).__init__() self.isMetadataPublished = {} self.isDataPublished = {} self.currentRow = None self.currentLayer = None self.parent = parent self.fieldsToPublish = {} self.metadata = {} execute(self._setupUi) for s in geodataServers().values(): s.setupForProject() def _setupUi(self): self.setupUi(self) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.layout().insertWidget(0, self.bar) self.populateComboBoxes() self.populateLayers() self.listLayers.setContextMenuPolicy(Qt.CustomContextMenu) self.listLayers.customContextMenuRequested.connect( self.showContextMenu) self.listLayers.currentRowChanged.connect(self.currentRowChanged) self.comboGeodataServer.currentIndexChanged.connect( self.geodataServerChanged) self.comboMetadataServer.currentIndexChanged.connect( self.metadataServerChanged) self.btnPublish.clicked.connect(self.publish) self.btnPublishOnBackground.clicked.connect(self.publishOnBackground) self.btnOpenQgisMetadataEditor.clicked.connect(self.openMetadataEditor) self.labelSelect.linkActivated.connect(self.selectLabelClicked) self.btnRemoveAll.clicked.connect(self.unpublishAll) self.btnRemoveAll.setIcon(REMOVE_ICON) self.btnValidate.setIcon(VALIDATE_ICON) self.btnPreview.clicked.connect(self.previewMetadata) self.btnPreview.setIcon(PREVIEW_ICON) self.btnSave.setIcon(SAVE_ICON) self.btnSave.setVisible(False) self.btnValidate.clicked.connect(self.validateMetadata) self.btnUseConstraints.clicked.connect( lambda: self.openMetadataEditor(ACCESS)) self.btnAccessConstraints.clicked.connect( lambda: self.openMetadataEditor(ACCESS)) self.btnIsoTopic.clicked.connect( lambda: self.openMetadataEditor(CATEGORIES)) self.btnKeywords.clicked.connect( lambda: self.openMetadataEditor(KEYWORDS)) self.btnDataContact.clicked.connect( lambda: self.openMetadataEditor(CONTACT)) self.btnMetadataContact.clicked.connect( lambda: self.openMetadataEditor(CONTACT)) if self.listLayers.count(): item = self.listLayers.item(0) self.listLayers.setCurrentItem(item) self.currentRowChanged(0) self.metadataServerChanged() self.selectLabelClicked("all") def geodataServerChanged(self): self.updateLayersPublicationStatus(True, False) def metadataServerChanged(self): self.updateLayersPublicationStatus(False, True) try: profile = metadataServers()[ self.comboMetadataServer.currentText()].profile except KeyError: profile = GeonetworkServer.PROFILE_DEFAULT if profile == GeonetworkServer.PROFILE_DEFAULT: if self.tabWidgetMetadata.count() == 3: self.tabWidgetMetadata.removeTab(1) self.tabWidgetMetadata.removeTab(1) else: if self.tabWidgetMetadata.count() == 1: title = "Dutch geography" if profile == GeonetworkServer.PROFILE_DUTCH else "INSPIRE" self.tabWidgetMetadata.addTab(self.tabInspire, title) self.tabWidgetMetadata.addTab(self.tabTemporal, self.tr("Temporal")) self.comboStatus.setVisible( profile == GeonetworkServer.PROFILE_DUTCH) def selectLabelClicked(self, url): state = Qt.Unchecked if url == "none" else Qt.Checked for i in range(self.listLayers.count()): item = self.listLayers.item(i) widget = self.listLayers.itemWidget(item).setCheckState(state) def currentRowChanged(self, currentRow): if self.currentRow == currentRow: return self.currentRow = currentRow self.storeFieldsToPublish() self.storeMetadata() layers = self.publishableLayers() layer = layers[currentRow] self.currentLayer = layer self.populateLayerMetadata() self.populateLayerFields() if layer.type() != layer.VectorLayer: self.tabLayerInfo.setCurrentWidget(self.tabMetadata) def populateLayerMetadata(self): metadata = self.metadata[self.currentLayer] self.txtMetadataTitle.setText(metadata.title()) self.txtAbstract.setPlainText(metadata.abstract()) isoTopics = ",".join(metadata.keywords().get("gmd:topicCategory", [])) self.txtIsoTopic.setText(isoTopics) keywords = [] for group in metadata.keywords().values(): keywords.extend(group) self.txtKeywords.setText(",".join(keywords)) if metadata.contacts(): self.txtDataContact.setText(metadata.contacts()[0].name) self.txtMetadataContact.setText(metadata.contacts()[0].name) self.txtUseConstraints.setText(metadata.fees()) licenses = metadata.licenses() if licenses: self.txtAccessConstraints.setText(licenses[0]) else: self.txtAccessConstraints.setText("") self.comboLanguage.setCurrentText(metadata.language()) #TODO: Use default values if no values in QGIS metadata object def populateLayerFields(self): if self.currentLayer.type() == self.currentLayer.VectorLayer: fields = [f.name() for f in self.currentLayer.fields()] self.tabLayerInfo.setTabEnabled(1, True) self.tableFields.setRowCount(len(fields)) for i, field in enumerate(fields): item = QTableWidgetItem() item.setFlags(item.flags() ^ Qt.ItemIsEditable) check = Qt.Checked if self.fieldsToPublish[ self.currentLayer][field] else Qt.Unchecked item.setCheckState(check) self.tableFields.setItem(i, 0, item) item = QTableWidgetItem(field) item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.tableFields.setItem(i, 1, item) else: self.tabLayerInfo.setTabEnabled(1, False) def storeMetadata(self): if self.currentLayer is not None: metadata = self.metadata[self.currentLayer] metadata.setTitle(self.txtMetadataTitle.text()) metadata.setAbstract(self.txtAbstract.toPlainText()) metadata.setLanguage(self.comboLanguage.currentText()) self.currentLayer.setMetadata(metadata) def storeFieldsToPublish(self): if self.currentLayer is not None: if self.currentLayer.type() == self.currentLayer.VectorLayer: fieldsToPublish = {} fields = self.currentLayer.fields() for i in range(fields.count()): chkItem = self.tableFields.item(i, 0) nameItem = self.tableFields.item(i, 1) fieldsToPublish[ nameItem.text()] = chkItem.checkState() == Qt.Checked self.fieldsToPublish[self.currentLayer] = fieldsToPublish def showContextMenu(self, pos): item = self.listLayers.itemAt(pos) if item is None: return row = self.listLayers.row(item) name = self.listLayers.itemWidget(item).name() menu = QMenu() if self.isDataPublished[name]: menu.addAction(self.tr("View WMS layer"), lambda: self.viewWms(name)) menu.addAction(self.tr("Unpublish data"), lambda: self.unpublishData(name)) if self.isMetadataPublished[name]: menu.addAction(self.tr("View metadata"), lambda: self.viewMetadata(name)) menu.addAction(self.tr("Unpublish metadata"), lambda: self.unpublishMetadata(name)) if any(self.isDataPublished.values()): menu.addAction(self.tr("View all WMS layers"), self.viewAllWms) menu.exec_(self.listLayers.mapToGlobal(pos)) def publishableLayers(self): layers = [ layer for layer in QgsProject.instance().mapLayers().values() if layer.type() in [QgsMapLayer.VectorLayer, QgsMapLayer.RasterLayer] ] return layers def populateLayers(self): layers = self.publishableLayers() for i, layer in enumerate(layers): fields = [f.name() for f in layer.fields() ] if layer.type() == layer.VectorLayer else [] self.fieldsToPublish[layer] = {f: True for f in fields} self.metadata[layer] = layer.metadata().clone() self.addLayerListItem(layer) self.updateLayersPublicationStatus() def addLayerListItem(self, layer): widget = LayerItemWidget(layer) item = QListWidgetItem(self.listLayers) item.setSizeHint(widget.sizeHint()) self.listLayers.addItem(item) self.listLayers.setItemWidget(item, widget) return item def populateComboBoxes(self): self.populatecomboMetadataServer() self.populatecomboGeodataServer() self.comboLanguage.clear() for lang in QgsMetadataWidget.parseLanguages(): self.comboLanguage.addItem(lang) def populatecomboGeodataServer(self): self.comboGeodataServer.clear() self.comboGeodataServer.addItem(self.tr("Do not publish data")) self.comboGeodataServer.addItems(geodataServers().keys()) def populatecomboMetadataServer(self): self.comboMetadataServer.clear() self.comboMetadataServer.addItem(self.tr("Do not publish metadata")) self.comboMetadataServer.addItems(metadataServers().keys()) def updateServers(self): #TODO: do not call updateLayersPublicationStatus if not really needed self.comboGeodataServer.currentIndexChanged.disconnect( self.geodataServerChanged) self.comboMetadataServer.currentIndexChanged.disconnect( self.metadataServerChanged) self.populatecomboMetadataServer() current = self.comboGeodataServer.currentText() self.populatecomboGeodataServer() if current in geodataServers().keys(): self.comboGeodataServer.setCurrentText(current) current = self.comboMetadataServer.currentText() self.populatecomboMetadataServer() if current in metadataServers().keys(): self.comboMetadataServer.setCurrentText(current) self.updateLayersPublicationStatus() self.comboGeodataServer.currentIndexChanged.connect( self.geodataServerChanged) self.comboMetadataServer.currentIndexChanged.connect( self.metadataServerChanged) def isMetadataOnServer(self, layer): try: server = metadataServers()[self.comboMetadataServer.currentText()] self.comboMetadataServer.setStyleSheet("QComboBox {}") uuid = uuidForLayer(self.layerFromName(layer)) return server.metadataExists(uuid) except KeyError: self.comboMetadataServer.setStyleSheet("QComboBox {}") return False except: self.comboMetadataServer.setStyleSheet( "QComboBox { border: 2px solid red; }") def isDataOnServer(self, layer): try: server = geodataServers()[self.comboGeodataServer.currentText()] self.comboGeodataServer.setStyleSheet("QComboBox {}") return server.layerExists(layer) except KeyError: self.comboGeodataServer.setStyleSheet("QComboBox {}") return False except Exception as e: self.comboGeodataServer.setStyleSheet( "QComboBox { border: 2px solid red; }") def validateMetadata(self): if self.currentLayer is None: return self.storeMetadata() validator = QgsNativeMetadataValidator() result, errors = validator.validate(self.metadata[self.currentLayer]) if result: txt = self.tr("No validation errors") else: txt = self.tr( "The following issues were found:") + "<br>" + "<br>".join([ "<b>%s</b>:%s" % (err.section, err.note) for err in errors ]) dlg = QgsMessageOutput.createMessageOutput() dlg.setTitle(self.tr("Metadata validation")) dlg.setMessage(txt, QgsMessageOutput.MessageHtml) dlg.showMessage() def openMetadataEditor(self, tab): if self.currentLayer is None: return self.storeMetadata() metadata = self.metadata[self.currentLayer].clone() w = MetadataDialog(metadata, tab, self) w.exec_() if w.metadata is not None: self.metadata[self.currentLayer] = w.metadata self.populateLayerMetadata() def unpublishData(self, name): server = geodataServers()[self.comboGeodataServer.currentText()] server.deleteLayer(name) server.deleteStyle(name) self.updateLayerIsDataPublished(name, False) def unpublishMetadata(self, name): server = metadataServers()[self.comboMetadataServer.currentText()] uuid = uuidForLayer(self.layerFromName(name)) server.deleteMetadata(uuid) self.updateLayerIsMetadataPublished(name, False) def updateLayerIsMetadataPublished(self, name, value): self.isMetadataPublished[name] = value for i in range(self.listLayers.count()): item = self.listLayers.item(i) widget = self.listLayers.itemWidget(item) if widget.name() == name: server = metadataServers()[ self.comboMetadataServer.currentText()] if value else None widget.setMetadataPublished(server) def updateLayerIsDataPublished(self, name, value): self.isDataPublished[name] = value for i in range(self.listLayers.count()): item = self.listLayers.item(i) widget = self.listLayers.itemWidget(item) if widget.name() == name: server = geodataServers()[ self.comboGeodataServer.currentText()] if value else None widget.setDataPublished(server) def updateLayersPublicationStatus(self, data=True, metadata=True): for i in range(self.listLayers.count()): item = self.listLayers.item(i) widget = self.listLayers.itemWidget(item) name = widget.name() if data: self.isDataPublished[name] = self.isDataOnServer(name) server = geodataServers()[self.comboGeodataServer.currentText( )] if self.isDataPublished[name] else None widget.setDataPublished(server) if metadata: self.isMetadataPublished[name] = self.isMetadataOnServer(name) server = metadataServers()[ self.comboMetadataServer.currentText( )] if self.isMetadataPublished[name] else None widget.setMetadataPublished(server) def unpublishAll(self): for name in self.isDataPublished: if self.isDataPublished[name]: self.unpublishData(name) if self.isMetadataPublished[name]: self.unpublishMetadata(name) def viewWms(self, name): layer = self.layerFromName(name) names = [layer.name()] bbox = layer.extent() if bbox.isEmpty(): bbox.grow(1) sbbox = ",".join([ str(v) for v in [ bbox.xMinimum(), bbox.yMinimum(), bbox.xMaximum(), bbox.yMaximum() ] ]) server = geodataServers()[self.comboGeodataServer.currentText()] server.openPreview(names, sbbox, layer.crs().authid()) def viewAllWms(self): server = geodataServers()[self.comboGeodataServer.currentText()] layers = self.publishableLayers() bbox = QgsRectangle() canvasCrs = iface.mapCanvas().mapSettings().destinationCrs() names = [] for layer in layers: if self.isDataPublished[layer.name()]: names.append(layer.name()) xform = QgsCoordinateTransform(layer.crs(), canvasCrs, QgsProject.instance()) extent = xform.transform(layer.extent()) bbox.combineExtentWith(extent) sbbox = ",".join([ str(v) for v in [ bbox.xMinimum(), bbox.yMinimum(), bbox.xMaximum(), bbox.yMaximum() ] ]) server.openPreview(names, sbbox, canvasCrs.authid()) def previewMetadata(self): if self.currentLayer is None: return html = self.currentLayer.htmlMetadata() dlg = QgsMessageOutput.createMessageOutput() dlg.setTitle("Layer metadata") dlg.setMessage(html, QgsMessageOutput.MessageHtml) dlg.showMessage() def viewMetadata(self, name): server = geodataServers()[self.comboMetadataServer.currentText()] layer = self.layerFromName(name) uuid = uuidForLayer(layer) server.openMetadata(uuid) def publish(self): progressMessageBar = self.bar.createMessage( self.tr("Publishing layers")) progress = QProgressBar() progress.setMaximum(100) progress.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) progressMessageBar.layout().addWidget(progress) self.bar.pushWidget(progressMessageBar, Qgis.Info) QCoreApplication.processEvents() task = self.getPublishTask(self.parent) task.progressChanged.connect(progress.setValue) ret = execute(task.run) self.bar.clearWidgets() task.finished(ret) if task.exception is not None: self.bar.clearWidgets() self.bar.pushMessage(self.tr("Error while publishing"), self.tr("See QGIS log for details"), level=Qgis.Warning, duration=5) QgsMessageLog.logMessage(task.exception, 'GeoCat Bridge', level=Qgis.Critical) self.updateLayersPublicationStatus(task.geodataServer is not None, task.metadataServer is not None) def publishOnBackground(self): self.parent.close() task = self.getPublishTask(iface.mainWindow()) def _finished(): if task.exception is not None: iface.messageBar().pushMessage( self.tr("Error while publishing"), self.tr("See QGIS log for details"), level=Qgis.Warning, duration=5) QgsMessageLog.logMessage(task.exception, 'GeoCat Bridge', level=Qgis.Critical) task.taskTerminated.connect(_finished) QgsApplication.taskManager().addTask(task) QCoreApplication.processEvents() def getPublishTask(self, parent): if self.comboGeodataServer.currentIndex() != 0: geodataServer = geodataServers()[ self.comboGeodataServer.currentText()] else: geodataServer = None if self.comboMetadataServer.currentIndex() != 0: metadataServer = metadataServers()[ self.comboMetadataServer.currentText()] else: metadataServer = None self.storeMetadata() self.storeFieldsToPublish() toPublish = [] for i in range(self.listLayers.count()): item = self.listLayers.item(i) widget = self.listLayers.itemWidget(item) if widget.checked(): name = widget.name() toPublish.append(name) onlySymbology = self.chkOnlySymbology.checkState() == Qt.Checked return PublishTask(toPublish, self.fieldsToPublish, onlySymbology, geodataServer, metadataServer, parent) def layerFromName(self, name): layers = self.publishableLayers() for layer in layers: if layer.name() == name: return layer