Ejemplo n.º 1
0
def toggle_select_features_widget(title, text, button_text, layer,
                                  new_feature_ids, old_feature_ids):
    """
    Create a widget for QgsMessageBar to switch between two sets.

    :param title: The title
    :type title: str
    :param text: The text message
    :type text: str
    :param button_text: The text on the toggle button
    :type button_text: str
    :param layer: The QgsVectorLayer where the selection is applied
    :type layer: QgsVectorLayer
    :param new_feature_ids: The list to select if use_new is true
    :type new_feature_ids: QgsFeatureIds
    :param old_feature_ids: The list to select if use_new is false
    :type old_feature_ids: QgsFeatureIds

    :returns: the widget
    """
    widget = QgsMessageBar.createMessage(title, text)
    button = QToolButton(widget)
    button.setCheckable(True)
    button.setText(button_text)
    button.toggled.connect(
        lambda on, layer=layer, new_feature_ids=new_feature_ids,
        old_feature_ids=old_feature_ids: toggle_select_features(
            layer, on, new_feature_ids, old_feature_ids))
    widget.layout().addWidget(button)
    return widget
    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)
Ejemplo n.º 3
0
def toggle_select_features_widget(title, text, button_text, layer,
                                  new_feature_ids, old_feature_ids):
    """
    Create a widget for QgsMessageBar to switch between two sets.

    :param title: The title
    :type title: str
    :param text: The text message
    :type text: str
    :param button_text: The text on the toggle button
    :type button_text: str
    :param layer: The QgsVectorLayer where the selection is applied
    :type layer: QgsVectorLayer
    :param new_feature_ids: The list to select if use_new is true
    :type new_feature_ids: QgsFeatureIds
    :param old_feature_ids: The list to select if use_new is false
    :type old_feature_ids: QgsFeatureIds
    """
    widget = QgsMessageBar.createMessage(title, text)
    button = QToolButton(widget)
    button.setCheckable(True)
    button.setText(button_text)
    button.toggled.connect(
        lambda on,
        layer=layer,
        new_feature_ids=new_feature_ids,
        old_feature_ids=old_feature_ids:
        toggle_select_features(layer,
                               on,
                               new_feature_ids,
                               old_feature_ids))
    widget.layout().addWidget(button)
    return widget
Ejemplo n.º 4
0
 def activate(self):
     """
     Map tool is activated
     """
     QgsMapTool.activate(self)
     self.canvas.setCursor(QCursor(Qt.CrossCursor))
     msgtitle = self.tr('Digitizing Drainage Channel')
     msg = self.tr('Digitize start and end point. Rightclick to abort.')
     self.messageBarItem = QgsMessageBar.createMessage(msgtitle, msg)
     self.iface.messageBar().pushItem(self.messageBarItem)
 def activate(self):
     """
     Map tool is activated
     """
     QgsMapTool.activate(self)
     self.canvas.setCursor(QCursor(Qt.CrossCursor))
     msgtitle = self.tr('Digitizing Drainage Channel')
     msg = self.tr('Digitize start and end point. Rightclick to abort.')
     self.messageBarItem = QgsMessageBar.createMessage(msgtitle,
                                                       msg)
     self.iface.messageBar().pushItem(self.messageBarItem)
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)
Ejemplo n.º 7
0
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: &copy; <a href="http://www.openstreetmap.org/copyright">OpenTopoMap</a>, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <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: &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <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: &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <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': '&copy; <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)
Ejemplo n.º 8
0
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
    def exportGeorefRaster(self, layer, rasterPath, isPutRotationInWorldFile,
                           isExportOnlyWorldFile):
        baseRasterFilePath, _ = os.path.splitext(rasterPath)
        # suppose supported format already checked
        rasterFormat = utils.imageFormat(rasterPath)

        try:
            originalWidth = layer.image.width()
            originalHeight = layer.image.height()
            radRotation = layer.rotation * math.pi / 180

            if isPutRotationInWorldFile or isExportOnlyWorldFile:
                # keep the image as is and put all transformation params
                # in world file
                img = layer.image

                a = layer.xScale * math.cos(radRotation)
                # sin instead of -sin because angle in CW
                b = -layer.yScale * math.sin(radRotation)
                d = layer.xScale * -math.sin(radRotation)
                e = -layer.yScale * math.cos(radRotation)
                c = layer.center.x() - (a * (originalWidth - 1) / 2 + b *
                                        (originalHeight - 1) / 2)
                f = layer.center.y() - (d * (originalWidth - 1) / 2 + e *
                                        (originalHeight - 1) / 2)

            else:
                # transform the image with rotation and scaling between the
                # axes
                # maintain at least the original resolution of the raster
                ratio = layer.xScale / layer.yScale
                if ratio > 1:
                    # increase x
                    scaleX = ratio
                    scaleY = 1
                else:
                    # increase y
                    scaleX = 1
                    scaleY = 1.0 / ratio

                width = abs(
                    scaleX * originalWidth * math.cos(radRotation)) + abs(
                        scaleY * originalHeight * math.sin(radRotation))
                height = abs(
                    scaleX * originalWidth * math.sin(radRotation)) + abs(
                        scaleY * originalHeight * math.cos(radRotation))

                qDebug("wh %f,%f" % (width, height))

                img = QImage(QSize(math.ceil(width), math.ceil(height)),
                             QImage.Format_ARGB32)
                # transparent background
                img.fill(QColor(0, 0, 0, 0))

                painter = QPainter(img)
                painter.setRenderHint(QPainter.Antialiasing, True)
                # painter.setRenderHint(QPainter.SmoothPixmapTransform, True)

                rect = QRectF(
                    QPointF(-layer.image.width() / 2.0,
                            -layer.image.height() / 2.0),
                    QPointF(layer.image.width() / 2.0,
                            layer.image.height() / 2.0),
                )

                painter.translate(QPointF(width / 2.0, height / 2.0))
                painter.rotate(layer.rotation)
                painter.scale(scaleX, scaleY)
                painter.drawImage(rect, layer.image)
                painter.end()

                extent = layer.extent()
                a = extent.width() / width
                e = -extent.height() / height
                # 2nd term because (0,0) of world file is on center of upper
                # left pixel instead of upper left corner of that pixel
                c = extent.xMinimum() + a / 2
                f = extent.yMaximum() + e / 2
                b = d = 0.0

            if not isExportOnlyWorldFile:
                # export image
                if rasterFormat == "tif":
                    writer = QImageWriter()
                    # use LZW compression for tiff
                    # useful for scanned documents (mostly white)
                    writer.setCompression(1)
                    writer.setFormat(b"TIFF")
                    writer.setFileName(rasterPath)
                    writer.write(img)
                else:
                    img.save(rasterPath, rasterFormat)

            worldFilePath = baseRasterFilePath + "."
            if rasterFormat == "jpg":
                worldFilePath += "jgw"
            elif rasterFormat == "png":
                worldFilePath += "pgw"
            elif rasterFormat == "bmp":
                worldFilePath += "bpw"
            elif rasterFormat == "tif":
                worldFilePath += "tfw"

            with open(worldFilePath, "w") as writer:
                # order is as described at
                # http://webhelp.esri.com/arcims/9.3/General/topics/author_world_files.htm
                writer.write("%.13f\n%.13f\n%.13f\n%.13f\n%.13f\n%.13f" %
                             (a, d, b, e, c, f))

            crsFilePath = rasterPath + ".aux.xml"
            with open(crsFilePath, "w") as writer:
                writer.write(
                    self.auxContent(
                        self.iface.mapCanvas().mapSettings().destinationCrs()))

            widget = QgsMessageBar.createMessage(
                "Raster Geoferencer", "Raster exported successfully.")
            self.iface.messageBar().pushWidget(widget, Qgis.Info, 2)
        except Exception as ex:
            QgsMessageLog.logMessage(repr(ex))
            widget = QgsMessageBar.createMessage(
                "Raster Geoferencer",
                "There was an error performing this command. "
                "See QGIS Message log for details.",
            )
            self.iface.messageBar().pushWidget(widget, Qgis.Critical, 5)
    def exportGeorefRaster(self, layer, rasterPath, isPutRotationInWorldFile):
        rasterFormat = rasterPath[-3:]

        try:
            originalWidth = layer.image.width()
            originalHeight = layer.image.height()
            radRotation = layer.rotation * math.pi / 180

            if isPutRotationInWorldFile:
                # keep the image as is and put all transformation params
                # in world file
                img = layer.image

                a = layer.xScale * math.cos(radRotation)
                # sin instead of -sin because angle in CW
                b = -layer.yScale * math.sin(radRotation)
                d = layer.xScale * -math.sin(radRotation)
                e = -layer.yScale * math.cos(radRotation)
                c = layer.center.x() - (a * (originalWidth - 1) / 2 +
                                        b * (originalHeight - 1) / 2)
                f = layer.center.y() - (d * (originalWidth - 1) / 2 +
                                        e * (originalHeight - 1) / 2)

            else:
                # transform the image with rotation and scaling between the
                # axes
                # maintain at least the original resolution of the raster
                ratio = layer.xScale / layer.yScale
                if ratio > 1:
                    # increase x
                    scaleX = ratio
                    scaleY = 1
                else:
                    # increase y
                    scaleX = 1
                    scaleY = 1. / ratio

                width = (abs(scaleX * originalWidth * math.cos(radRotation)) +
                         abs(scaleY * originalHeight * math.sin(radRotation)))
                height = (abs(scaleX * originalWidth * math.sin(radRotation)) +
                          abs(scaleY * originalHeight * math.cos(radRotation)))

                qDebug("wh %f,%f" % (width, height))

                img = QImage(QSize(math.ceil(width), math.ceil(height)),
                             QImage.Format_ARGB32)
                # transparent background
                img.fill(QColor(0, 0, 0, 0))

                painter = QPainter(img)
                painter.setRenderHint(QPainter.Antialiasing, True)

                rect = QRectF(QPointF(-layer.image.width() / 2.0,
                                      -layer.image.height() / 2.0),
                              QPointF(layer.image.width() / 2.0,
                                      layer.image.height() / 2.0))

                painter.translate(QPointF(width / 2.0, height / 2.0))
                painter.rotate(layer.rotation)
                painter.scale(scaleX, scaleY)
                painter.drawImage(rect, layer.image)
                painter.end()

                extent = layer.extent()
                a = extent.width() / width
                e = -extent.height() / height
                # 2nd term because (0,0) of world file is on center of upper
                # left pixel instead of upper left corner of that pixel
                c = extent.xMinimum() + a / 2
                f = extent.yMaximum() + e / 2
                b = d = 0.0

            img.save(rasterPath, rasterFormat)

            worldFilePath = rasterPath[:-3]
            if rasterFormat == "jpg":
                worldFilePath += "jgw"
            elif rasterFormat == "png":
                worldFilePath += "pgw"
            elif rasterFormat == "bmp":
                worldFilePath += "bpw"
            elif rasterFormat == "tif":
                worldFilePath += "tfw"

            with open(worldFilePath, "w") as writer:
                writer.write("%.13f\n%.13f\n%.13f\n%.13f\n%.13f\n%.13f" %
                             (a, b, d, e, c, f))

            crsFilePath = rasterPath + ".aux.xml"
            with open(crsFilePath, "w") as writer:
                writer.write(self.auxContent(
                    self.iface.mapCanvas().mapSettings().destinationCrs()))

            widget = QgsMessageBar.createMessage(
                "Raster Geoferencer", "Raster exported successfully.")
            self.iface.messageBar().pushWidget(widget,  Qgis.Info, 2)
        except Exception as ex:
            QgsMessageLog.logMessage(repr(ex))
            widget = QgsMessageBar.createMessage(
                "Raster Geoferencer",
                "There was an error performing this command. "
                "See QGIS Message log for details.")
            self.iface.messageBar().pushWidget(
                widget, Qgis.Critical, 5)
    def exportGeorefRaster(self, layer, rasterPath, isPutRotationInWorldFile):
        rasterFormat = rasterPath[-3:]

        try:
            originalWidth = layer.image.width()
            originalHeight = layer.image.height()
            radRotation = layer.rotation * math.pi / 180

            if isPutRotationInWorldFile:
                # keep the image as is and put all transformation params
                # in world file
                img = layer.image

                a = layer.xScale * math.cos(radRotation)
                # sin instead of -sin because angle in CW
                b = -layer.yScale * math.sin(radRotation)
                d = layer.xScale * -math.sin(radRotation)
                e = -layer.yScale * math.cos(radRotation)
                c = layer.center.x() - (a * (originalWidth - 1) / 2 + b *
                                        (originalHeight - 1) / 2)
                f = layer.center.y() - (d * (originalWidth - 1) / 2 + e *
                                        (originalHeight - 1) / 2)

            else:
                # transform the image with rotation and scaling between the
                # axes
                # maintain at least the original resolution of the raster
                ratio = layer.xScale / layer.yScale
                if ratio > 1:
                    # increase x
                    scaleX = ratio
                    scaleY = 1
                else:
                    # increase y
                    scaleX = 1
                    scaleY = 1. / ratio

                width = (abs(scaleX * originalWidth * math.cos(radRotation)) +
                         abs(scaleY * originalHeight * math.sin(radRotation)))
                height = (abs(scaleX * originalWidth * math.sin(radRotation)) +
                          abs(scaleY * originalHeight * math.cos(radRotation)))

                qDebug("wh %f,%f" % (width, height))

                img = QImage(QSize(math.ceil(width), math.ceil(height)),
                             QImage.Format_ARGB32)
                # transparent background
                img.fill(QColor(0, 0, 0, 0))

                painter = QPainter(img)
                painter.setRenderHint(QPainter.Antialiasing, True)

                rect = QRectF(
                    QPointF(-layer.image.width() / 2.0,
                            -layer.image.height() / 2.0),
                    QPointF(layer.image.width() / 2.0,
                            layer.image.height() / 2.0))

                painter.translate(QPointF(width / 2.0, height / 2.0))
                painter.rotate(layer.rotation)
                painter.scale(scaleX, scaleY)
                painter.drawImage(rect, layer.image)
                painter.end()

                extent = layer.extent()
                a = extent.width() / width
                e = -extent.height() / height
                # 2nd term because (0,0) of world file is on center of upper
                # left pixel instead of upper left corner of that pixel
                c = extent.xMinimum() + a / 2
                f = extent.yMaximum() + e / 2
                b = d = 0.0

            img.save(rasterPath, rasterFormat)

            worldFilePath = rasterPath[:-3]
            if rasterFormat == "jpg":
                worldFilePath += "jgw"
            elif rasterFormat == "png":
                worldFilePath += "pgw"
            elif rasterFormat == "bmp":
                worldFilePath += "bpw"
            elif rasterFormat == "tif":
                worldFilePath += "tfw"

            with open(worldFilePath, "w") as writer:
                writer.write("%.13f\n%.13f\n%.13f\n%.13f\n%.13f\n%.13f" %
                             (a, b, d, e, c, f))

            crsFilePath = rasterPath + ".aux.xml"
            with open(crsFilePath, "w") as writer:
                writer.write(
                    self.auxContent(
                        self.iface.mapCanvas().mapSettings().destinationCrs()))

            widget = QgsMessageBar.createMessage(
                "Raster Geoferencer", "Raster exported successfully.")
            self.iface.messageBar().pushWidget(widget, Qgis.Info, 2)
        except Exception as ex:
            QgsMessageLog.logMessage(repr(ex))
            widget = QgsMessageBar.createMessage(
                "Raster Geoferencer",
                "There was an error performing this command. "
                "See QGIS Message log for details.")
            self.iface.messageBar().pushWidget(widget, Qgis.Critical, 5)
Ejemplo n.º 12
0
 def activate(self):
     QgsMapTool.activate( self )
     self.canvas.setCursor( QCursor( Qt.CrossCursor ) )
     self.messageBarItem = QgsMessageBar.createMessage( self.tr('Digitizing Drainage Channel'), self.tr('Digitize start and end point. Rightclick to abort.') )
     self.iface.messageBar().pushItem(self.messageBarItem)
     pass
Ejemplo n.º 13
0
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