def __init__(self, options, multiple, columns=2, parent=None): super(CheckboxesPanel, self).__init__(parent) self._options = [] for i, option in enumerate(options): if isinstance(option, str): self._options.append((i, option)) else: self.options.append(option) self._multiple = multiple self._buttons = [] rows = len(options) / columns self._buttonGroup = QButtonGroup() self._buttonGroup.setExclusive(not multiple) layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setMargin(0) for i, (v, t) in enumerate(self._options): if multiple: button = QCheckBox(t) else: button = QRadioButton(t) self._buttons.append((v, button)) self._buttonGroup.addButton(button, i) layout.addWidget(button, i % rows, i / rows) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, columns) self.setLayout(layout) if multiple: self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showPopupMenu)
def __init__(self, iface, parent=None): self.iface = iface self.plugin_dir = os.path.dirname(__file__) # Some constants self.SDELLIPSE = self.tr('SD Ellipse') self.CANCEL = self.tr('Cancel') self.HELP = self.tr('Help') self.CLOSE = self.tr('Close') self.OK = self.tr('OK') """Constructor.""" super(SDEllipseDialog, 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.setupUi(self) self.method_group = QButtonGroup() self.method_group.addButton(self.yuill_rb) self.method_group.addButton(self.crimestat_rb) self.method_group.buttonClicked[QAbstractButton].connect( self.methodChanged) self.yuill_rb.setChecked(True) self.method = 1 okButton = self.button_box.button(QDialogButtonBox.Ok) okButton.setText(self.OK) cancelButton = self.button_box.button(QDialogButtonBox.Cancel) cancelButton.setText(self.CANCEL) cancelButton.setEnabled(False) # helpButton = self.button_box.button(QDialogButtonBox.Help) helpButton = self.helpButton helpButton.setText(self.HELP) closeButton = self.button_box.button(QDialogButtonBox.Close) closeButton.setText(self.CLOSE) # Connect signals okButton.clicked.connect(self.startWorker) cancelButton.clicked.connect(self.killWorker) helpButton.clicked.connect(self.giveHelp) closeButton.clicked.connect(self.reject) self.cumulative = False inpIndexCh = self.InputLayer.currentIndexChanged['QString'] inpIndexCh.connect(self.layerchanged) # QObject.disconnect(self.button_box, SIGNAL("rejected()"), # self.reject) self.button_box.rejected.disconnect(self.reject) # Set instance variables self.worker = None self.inputlayerid = None self.layerlistchanging = False self.selectedFeatures_cb.setChecked(True) self.useWeights_cb.setChecked(False) self.result = None
def __init__(self, parent=None, iface=None): """Constructor for import dialog. .. versionadded: 3.3 :param parent: Optional widget to use as parent. :type parent: QWidget :param iface: An instance of QgisInterface. :type iface: QgisInterface """ QDialog.__init__(self, parent) self.parent = parent self.setupUi(self) title = self.tr('PetaBencana Downloader') self.setWindowTitle(title) icon = resources_path('img', 'icons', 'add-petabencana-layer.svg') self.setWindowIcon(QtGui.QIcon(icon)) self.iface = iface self.source = None self.radio_button_group = QButtonGroup() self.radio_button_group.addButton(self.radio_button_production) self.radio_button_group.addButton(self.radio_button_development) self.radio_button_group.setExclusive(True) self.radio_button_production.setChecked(True) self.populate_combo_box() developer_mode = setting('developer_mode', False, bool) if not developer_mode: self.radio_button_widget.hide() self.source_label.hide() self.output_group.adjustSize() # signals self.radio_button_production.clicked.connect(self.populate_combo_box) self.radio_button_development.clicked.connect(self.populate_combo_box) # Set up things for context help self.help_button = self.button_box.button( QtWidgets.QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) self.main_stacked_widget.setCurrentIndex(1) # set up the validator for the file name prefix expression = QRegExp('^[A-Za-z0-9-_]*$') validator = QtGui.QRegExpValidator(expression, self.filename_prefix) self.filename_prefix.setValidator(validator) self.time_stamp = None self.restore_state()
def __init__(self, parameter, parent=None): """Constructor. :param parameter: A DefaultValueParameter object. :type parameter: DefaultValueParameter """ super(DefaultValueParameterWidget, self).__init__(parameter, parent) self.radio_button_layout = QHBoxLayout() # Create radio button group self.input_button_group = QButtonGroup() for i in range(len(self._parameter.labels)): if '%s' in self._parameter.labels[i]: label = ( self._parameter.labels[i] % self._parameter.options[i]) else: label = self._parameter.labels[i] radio_button = QRadioButton(label) self.radio_button_layout.addWidget(radio_button) self.input_button_group.addButton(radio_button, i) if self._parameter.value == \ self._parameter.options[i]: radio_button.setChecked(True) # Create double spin box for custom value self.custom_value = QDoubleSpinBox() self.custom_value.setSingleStep(0.1) if self._parameter.options[-1]: self.custom_value.setValue(self._parameter.options[-1]) self.radio_button_layout.addWidget(self.custom_value) self.toggle_custom_value() self.inner_input_layout.addLayout(self.radio_button_layout) # Connect # noinspection PyUnresolvedReferences self.input_button_group.buttonClicked.connect( self.toggle_custom_value)
class CheckboxesPanel(QWidget): def __init__(self, options, multiple, columns=2, parent=None): super(CheckboxesPanel, self).__init__(parent) self._options = [] for i, option in enumerate(options): if isinstance(option, str): self._options.append((i, option)) else: self.options.append(option) self._multiple = multiple self._buttons = [] rows = len(options) / columns self._buttonGroup = QButtonGroup() self._buttonGroup.setExclusive(not multiple) layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setMargin(0) for i, (v, t) in enumerate(self._options): if multiple: button = QCheckBox(t) else: button = QRadioButton(t) self._buttons.append((v, button)) self._buttonGroup.addButton(button, i) layout.addWidget(button, i % rows, i / rows) layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, columns) self.setLayout(layout) if multiple: self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showPopupMenu) def showPopupMenu(self): popup_menu = QMenu() select_all_action = QAction(self.tr('Select All'), popup_menu) select_all_action.triggered.connect(self.selectAll) clear_all_action = QAction(self.tr('Clear Selection'), popup_menu) clear_all_action.triggered.connect(self.deselectAll) popup_menu.addAction(select_all_action) popup_menu.addAction(clear_all_action) popup_menu.exec_(QCursor.pos()) def selectAll(self): for (v, button) in self._buttons: button.setChecked(True) def deselectAll(self): for (v, button) in self._buttons: button.setChecked(False) def value(self): if self._multiple: value = [] for (v, checkbox) in self._buttons: if checkbox.isChecked(): value.append(v) return value else: return self._options[self._buttonGroup.checkedId()][0] def setValue(self, value): if self._multiple: for (v, button) in self._buttons: if v in value: button.setChecked(True) else: for v, button in self._buttons: if v == value: button.setChecked(True)
class SDEllipseDialog(QDialog, FORM_CLASS): def __init__(self, iface, parent=None): self.iface = iface self.plugin_dir = os.path.dirname(__file__) # Some constants self.SDELLIPSE = self.tr('SD Ellipse') self.CANCEL = self.tr('Cancel') self.HELP = self.tr('Help') self.CLOSE = self.tr('Close') self.OK = self.tr('OK') """Constructor.""" super(SDEllipseDialog, 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.setupUi(self) self.method_group = QButtonGroup() self.method_group.addButton(self.yuill_rb) self.method_group.addButton(self.crimestat_rb) self.method_group.buttonClicked[QAbstractButton].connect( self.methodChanged) self.yuill_rb.setChecked(True) self.method = 1 okButton = self.button_box.button(QDialogButtonBox.Ok) okButton.setText(self.OK) cancelButton = self.button_box.button(QDialogButtonBox.Cancel) cancelButton.setText(self.CANCEL) cancelButton.setEnabled(False) # helpButton = self.button_box.button(QDialogButtonBox.Help) helpButton = self.helpButton helpButton.setText(self.HELP) closeButton = self.button_box.button(QDialogButtonBox.Close) closeButton.setText(self.CLOSE) # Connect signals okButton.clicked.connect(self.startWorker) cancelButton.clicked.connect(self.killWorker) helpButton.clicked.connect(self.giveHelp) closeButton.clicked.connect(self.reject) self.cumulative = False inpIndexCh = self.InputLayer.currentIndexChanged['QString'] inpIndexCh.connect(self.layerchanged) # QObject.disconnect(self.button_box, SIGNAL("rejected()"), # self.reject) self.button_box.rejected.disconnect(self.reject) # Set instance variables self.worker = None self.inputlayerid = None self.layerlistchanging = False self.selectedFeatures_cb.setChecked(True) self.useWeights_cb.setChecked(False) self.result = None # end of __init__ def giveHelp(self): self.showInfo('Giving help') QDesktopServices.openUrl(QUrl.fromLocalFile( self.plugin_dir + "/help/html/index.html")) # showPluginHelp(None, "help/html/index") # end of giveHelp def startWorker(self): # self.showInfo('Ready to start worker') self.degfreedCorr = self.degfreedcorr_cb.isChecked() self.crimestatCorr = self.crimestatcorr_cb.isChecked() # Get the input layer layerindex = self.InputLayer.currentIndex() layerId = self.InputLayer.itemData(layerindex) inputlayer = QgsProject.instance().mapLayer(layerId) if inputlayer is None: self.showError(self.tr('No input layer defined')) return self.featureCount = 0 if self.selectedFeatures_cb.isChecked(): self.featureCount = inputlayer.selectedFeatureCount() if self.featureCount == 0: self.featureCount = inputlayer.featureCount() if self.featureCount < 2: self.showError(self.tr('Not enough features')) # self.scene.clear() return if (self.useWeights_cb.isChecked() and self.inputField.count() == 0): self.showError(self.tr('Missing numerical field')) return fieldindex = self.inputField.currentIndex() fieldname = self.inputField.itemData(fieldindex) # inpfield = inputlayer.fieldNameIndex(fieldindex) # minval = inputlayer.minimumValue(inpfield) if (not self.useWeights_cb.isChecked()): fieldname = None self.result = None self.SDLayer = inputlayer self.method = 1 if self.yuill_rb.isChecked(): self.method = 1 elif self.crimestat_rb.isChecked(): self.method = 2 if self.featureCount < 3 and (self.method == 2 or self.degfreedCorr): self.showError(self.tr('Not enough features')) return # create a new worker instance worker = Worker(inputlayer, self.selectedFeatures_cb.isChecked(), fieldname, self.method) # start the worker in a new thread thread = QThread(self) worker.moveToThread(thread) worker.finished.connect(self.workerFinished) worker.error.connect(self.workerError) worker.status.connect(self.workerInfo) worker.progress.connect(self.progressBar.setValue) # worker.progress.connect(self.aprogressBar.setValue) thread.started.connect(worker.run) thread.start() self.thread = thread self.worker = worker self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.button_box.button(QDialogButtonBox.Close).setEnabled(False) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True) self.InputLayer.setEnabled(False) self.inputField.setEnabled(False) # end of startWorker def workerFinished(self, ok, ret): """Handles the output from the worker and cleans up after the worker has finished.""" # clean up the worker and thread self.worker.deleteLater() self.thread.quit() self.thread.wait() self.thread.deleteLater() # remove widget from the message bar (pop) # self.iface.messageBar().popWidget(self.messageBar) if ok and ret is not None: # self.showInfo("Result: " + str(ret)) self.result = ret # Draw the ellipse self.drawEllipse() else: # notify the user that something went wrong if not ok: self.showError(self.tr('Aborted') + '!') else: self.showError(self.tr('Not able to create ellipse') + '!') # Update the user interface self.progressBar.setValue(0.0) self.button_box.button(QDialogButtonBox.Ok).setEnabled(True) self.button_box.button(QDialogButtonBox.Close).setEnabled(True) self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False) self.InputLayer.setEnabled(True) if self.method == 1 and self.inputField.count() > 0: self.inputField.setEnabled(True) # end of workerFinished(self, ok, ret) def workerError(self, exception_string): """Report an error from the worker.""" self.showError(self.tr('Worker failed - exception') + ': ' + exception_string) def workerInfo(self, message_string): """Report an info message from the worker.""" self.showInfo(self.tr('Worker') + ': ' + message_string) def killWorker(self): """Kill the worker thread.""" if self.worker is not None: QgsMessageLog.logMessage(self.tr('Killing worker'), self.SDELLIPSE, Qgis.Info) self.worker.kill() # Implement the reject method to have the possibility to avoid # exiting the dialog when cancelling def reject(self): """Reject override.""" # exit the dialog QDialog.reject(self) def drawEllipse(self): # self.showInfo('Result: ' + str(self.result)) meanx = self.result[0] meany = self.result[1] angle1 = self.result[2] angle2 = self.result[3] SD1 = self.result[4] SD2 = self.result[5] if self.method == 2: # CrimeStat SD1 = SD1 * (sqrt(2) * sqrt(self.featureCount) / sqrt(self.featureCount - 2)) SD2 = SD2 * (sqrt(2) * sqrt(self.featureCount) / sqrt(self.featureCount - 2)) if self.crimestatCorr and self.method != 2: SD1 = SD1 * sqrt(2) SD2 = SD2 * sqrt(2) if self.degfreedCorr and self.method != 2: SD1 = SD1 * sqrt(self.featureCount) / sqrt(self.featureCount - 2) SD2 = SD2 * sqrt(self.featureCount) / sqrt(self.featureCount - 2) # SD1 = SD1 * sqrt(self.featureCount) / sqrt(self.featureCount - 1) # SD2 = SD2 * sqrt(self.featureCount) / sqrt(self.featureCount - 1) # Find the major and minor axis majoraxisangle = angle1 minoraxisangle = angle2 majorSD = SD2 minorSD = SD1 if SD2 < SD1: majoraxisangle = angle2 minoraxisangle = angle1 majorSD = SD1 minorSD = SD2 # Calculate the "compass" direction angle (clockwise from north) direction = 90.0 - majoraxisangle * 180 / pi # Calculte the eccentricity eccentricity = sqrt(1 - pow(minorSD, 2) / pow(majorSD, 2)) # Create the memory layer for the ellipse sdefields = [] sdefields.append(QgsField("meanx", QVariant.Double)) sdefields.append(QgsField("meany", QVariant.Double)) sdefields.append(QgsField("majoranglerad", QVariant.Double)) # sdefields.append(QgsField("minoranglerad", QVariant.Double)) sdefields.append(QgsField("directiondeg", QVariant.Double)) sdefields.append(QgsField("majorsd", QVariant.Double)) sdefields.append(QgsField("minorsd", QVariant.Double)) sdefields.append(QgsField("eccentricity", QVariant.Double)) layeruri = 'Polygon?' layeruri = (layeruri + 'crs=' + str(self.SDLayer.dataProvider().crs().authid())) memSDlayer = QgsVectorLayer(layeruri, self.OutputLayerName.text(), "memory") # Set the CRS to the original CRS object memSDlayer.setCrs(self.SDLayer.dataProvider().crs()) memSDlayer.startEditing() # ? for field in sdefields: memSDlayer.dataProvider().addAttributes([field]) sdfeature = QgsFeature() theta1 = majoraxisangle points = [] step = pi / 180 # 360 points to draw the ellipse t = 0.0 while t < 2 * pi: p1 = QPointF(meanx + majorSD * cos(t) * cos(majoraxisangle) - minorSD * sin(t) * sin(majoraxisangle), meany + majorSD * cos(t) * sin(majoraxisangle) + minorSD * sin(t) * cos(majoraxisangle)) points.append(QgsPointXY(p1)) t = t + step # Close the polygon p1 = QPointF(meanx + majorSD * cos(majoraxisangle), meany + majorSD * sin(majoraxisangle)) points.append(QgsPointXY(p1)) sdfeature.setGeometry(QgsGeometry.fromPolygonXY([points])) attrs = [meanx, meany, majoraxisangle, direction, majorSD, minorSD, eccentricity] sdfeature.setAttributes(attrs) memSDlayer.dataProvider().addFeatures([sdfeature]) memSDlayer.commitChanges() # ? memSDlayer.updateExtents() QgsProject.instance().addMapLayers([memSDlayer]) # end of drawEllipse def layerchanged(self, number=0): """Do the necessary updates after a layer selection has been changed.""" self.layerselectionactive = True layerindex = self.InputLayer.currentIndex() layerId = self.InputLayer.itemData(layerindex) self.inputlayerid = layerId inputlayer = QgsProject.instance().mapLayer(layerId) while self.inputField.count() > 0: self.inputField.removeItem(0) self.useWeights_cb.setEnabled(False) self.inputField.setEnabled(False) self.layerselectionactive = False # Get the numerical fields if inputlayer is not None: provider = inputlayer.dataProvider() attribs = provider.fields() if str(type(attribs)) != "<type 'dict'>": atr = {} for i in range(attribs.count()): atr[i] = attribs.at(i) attrdict = atr for id, attrib in attrdict.items(): # Check for numeric attribute #if attrib.typeName().upper() in ('REAL', 'INTEGER', 'INT4', # 'INT8', 'FLOAT4'): if attrib.isNumeric(): self.inputField.addItem(attrib.name(), attrib.name()) if (self.inputField.count() > 0): if self.method == 1: self.useWeights_cb.setEnabled(True) self.inputField.setEnabled(True) else: self.useWeights_cb.setChecked(False) self.OutputLayerName.setText( "SDE_" + self.method_group.checkedButton().text().strip('"') + "_" + self.InputLayer.currentText()) # end of layerchanged def methodChanged(self, button): if self.InputLayer.currentText() is not None: self.OutputLayerName.setText("SDE_" + button.text().strip('"') + "_" + self.InputLayer.currentText()) # Disable all options self.crimestatcorr_cb.setEnabled(False) self.crimestatcorr_cb.setChecked(False) self.degfreedcorr_cb.setEnabled(False) self.degfreedcorr_cb.setChecked(False) self.useWeights_cb.setEnabled(False) self.useWeights_cb.setChecked(False) self.inputField.setEnabled(False) if button.text() == '"CrimeStat"': self.method = 2 elif button.text() == "Yuill": self.method = 1 self.crimestatcorr_cb.setEnabled(True) self.degfreedcorr_cb.setEnabled(True) if self.inputField.count() > 0: self.useWeights_cb.setEnabled(True) self.inputField.setEnabled(True) else: # Should not be reached yet if self.inputField.count() > 0: self.useWeights_cb.setEnabled(True) self.inputField.setEnabled(True) def showError(self, text): """Show an error.""" # self.iface.messageBar().pushMessage(self.tr('Error'), text, # level=QgsMessageBar.CRITICAL, # duration=3) QgsMessageLog.logMessage('Error: ' + text, self.SDELLIPSE, Qgis.Critical) def showWarning(self, text): """Show a warning.""" # self.iface.messageBar().pushMessage(self.tr('Warning'), text, # level=QgsMessageBar.WARNING, # duration=2) QgsMessageLog.logMessage('Warning: ' + text, self.SDELLIPSE, Qgis.Warning) def showInfo(self, text): """Show info.""" # self.iface.messageBar().pushMessage(self.tr('Info'), text, # level=QgsMessageBar.INFO, # duration=2) QgsMessageLog.logMessage('Info: ' + text, self.SDELLIPSE, Qgis.Info) # Implement the accept method to avoid exiting the dialog when # starting the work def accept(self): """Accept override.""" pass # Implement the reject method to have the possibility to avoid # exiting the dialog when cancelling def reject(self): """Reject override.""" # exit the dialog QDialog.reject(self) # Translation def tr(self, message): """Get the translation for a string using Qt translation API. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ return QCoreApplication.translate('Dialog', message) # Overriding def resizeEvent(self, event): return # self.showInfo("resizeEvent") # Overriding def showEvent(self, event): return
class PetaBencanaDialog(QDialog, FORM_CLASS): """Downloader for PetaBencana data. .. versionadded: 3.3 """ def __init__(self, parent=None, iface=None): """Constructor for import dialog. .. versionadded: 3.3 :param parent: Optional widget to use as parent. :type parent: QWidget :param iface: An instance of QgisInterface. :type iface: QgisInterface """ QDialog.__init__(self, parent) self.parent = parent self.setupUi(self) title = self.tr('PetaBencana Downloader') self.setWindowTitle(title) icon = resources_path('img', 'icons', 'add-petabencana-layer.svg') self.setWindowIcon(QtGui.QIcon(icon)) self.iface = iface self.source = None self.radio_button_group = QButtonGroup() self.radio_button_group.addButton(self.radio_button_production) self.radio_button_group.addButton(self.radio_button_development) self.radio_button_group.setExclusive(True) self.radio_button_production.setChecked(True) self.populate_combo_box() developer_mode = setting('developer_mode', False, bool) if not developer_mode: self.radio_button_widget.hide() self.source_label.hide() self.output_group.adjustSize() # signals self.radio_button_production.clicked.connect(self.populate_combo_box) self.radio_button_development.clicked.connect(self.populate_combo_box) # Set up things for context help self.help_button = self.button_box.button( QtWidgets.QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) self.main_stacked_widget.setCurrentIndex(1) # set up the validator for the file name prefix expression = QRegExp('^[A-Za-z0-9-_]*$') validator = QtGui.QRegExpValidator(expression, self.filename_prefix) self.filename_prefix.setValidator(validator) self.time_stamp = None self.restore_state() @pyqtSlot(bool) # prevents actions being handled twice def help_toggled(self, flag): """Show or hide the help tab in the stacked widget. .. versionadded: 3.3 :param flag: Flag indicating whether help should be shown or hidden. :type flag: bool """ if flag: self.help_button.setText(self.tr('Hide Help')) self.show_help() else: self.help_button.setText(self.tr('Show Help')) self.hide_help() def hide_help(self): """Hide the usage info from the user. .. versionadded:: 3.3 """ self.main_stacked_widget.setCurrentIndex(1) def show_help(self): """Show usage info to the user. .. versionadded: 3.3 """ # Read the header and footer html snippets self.main_stacked_widget.setCurrentIndex(0) header = html_header() footer = html_footer() string = header message = peta_bencana_help() string += message.to_html() string += footer self.help_web_view.setHtml(string) def restore_state(self): """Read last state of GUI from configuration file. .. versionadded: 3.3 """ settings = QSettings() try: last_path = settings.value('directory', type=str) except TypeError: last_path = '' self.output_directory.setText(last_path) def save_state(self): """Store current state of GUI to configuration file. .. versionadded: 3.3 """ settings = QSettings() settings.setValue('directory', self.output_directory.text()) @pyqtSlot() # prevents actions being handled twice def on_directory_button_clicked(self): """Show a dialog to choose directory. .. versionadded: 3.3 """ # noinspection PyCallByClass,PyTypeChecker self.output_directory.setText(QFileDialog.getExistingDirectory( self, self.tr('Select download directory'))) def accept(self): """Do PetaBencana download and display it in QGIS. .. versionadded: 3.3 """ self.save_state() try: self.require_directory() except CanceledImportDialogError: return QgsApplication.instance().setOverrideCursor( QtGui.QCursor(QtCore.Qt.WaitCursor) ) source = self.define_url() # save the file as json first name = 'jakarta_flood.json' output_directory = self.output_directory.text() output_prefix = self.filename_prefix.text() overwrite = self.overwrite_flag.isChecked() date_stamp_flag = self.include_date_flag.isChecked() output_base_file_path = self.get_output_base_path( output_directory, output_prefix, date_stamp_flag, name, overwrite) title = self.tr("Can't access API") try: self.download(source, output_base_file_path) # Open downloaded file as QgsMapLayer options = QgsVectorLayer.LayerOptions(False) layer = QgsVectorLayer( output_base_file_path, 'flood', 'ogr', options) except Exception as e: disable_busy_cursor() QMessageBox.critical(self, title, str(e)) return self.time_stamp = time.strftime('%d-%b-%Y %H:%M:%S') # Now save as shp name = 'jakarta_flood.shp' output_base_file_path = self.get_output_base_path( output_directory, output_prefix, date_stamp_flag, name, overwrite) QgsVectorFileWriter.writeAsVectorFormat( layer, output_base_file_path, 'CP1250', QgsCoordinateTransform(), 'ESRI Shapefile') # Get rid of the GeoJSON layer and rather use local shp del layer self.copy_style(output_base_file_path) self.copy_keywords(output_base_file_path) layer = self.add_flooded_field(output_base_file_path) # check if the layer has feature or not if layer.featureCount() <= 0: city = self.city_combo_box.currentText() message = self.tr( 'There are no floods data available on {city} ' 'at this time.').format(city=city) display_warning_message_box( self, self.tr('No data'), message) disable_busy_cursor() else: # add the layer to the map project = QgsProject.instance() project.addMapLayer(layer) disable_busy_cursor() self.done(QDialog.Accepted) def add_flooded_field(self, shapefile_path): """Create the layer from the local shp adding the flooded field. .. versionadded:: 3.3 Use this method to add a calculated field to a shapefile. The shapefile should have a field called 'count' containing the number of flood reports for the field. The field values will be set to 0 if the count field is < 1, otherwise it will be set to 1. :param shapefile_path: Path to the shapefile that will have the flooded field added. :type shapefile_path: basestring :return: A vector layer with the flooded field added. :rtype: QgsVectorLayer """ layer = QgsVectorLayer( shapefile_path, self.tr('Jakarta Floods'), 'ogr') # Add a calculated field indicating if a poly is flooded or not # from qgis.PyQt.QtCore import QVariant layer.startEditing() # Add field with integer from 0 to 4 which represents the flood # class. Its the same as 'state' field except that is being treated # as a string. # This is used for cartography flood_class_field = QgsField('floodclass', QVariant.Int) layer.addAttribute(flood_class_field) layer.commitChanges() layer.startEditing() flood_class_idx = layer.fields().lookupField('floodclass') flood_class_expression = QgsExpression('to_int(state)') context = QgsExpressionContext() context.setFields(layer.fields()) flood_class_expression.prepare(context) # Add field with boolean flag to say if the area is flooded # This is used by the impact function flooded_field = QgsField('flooded', QVariant.Int) layer.dataProvider().addAttributes([flooded_field]) layer.commitChanges() layer.startEditing() flooded_idx = layer.fields().lookupField('flooded') flood_flag_expression = QgsExpression('state > 0') flood_flag_expression.prepare(context) for feature in layer.getFeatures(): context.setFeature(feature) feature[flood_class_idx] = flood_class_expression.evaluate(context) feature[flooded_idx] = flood_flag_expression.evaluate(context) layer.updateFeature(feature) layer.commitChanges() return layer def copy_keywords(self, shapefile_path): """Copy keywords from the OSM resource directory to the output path. .. versionadded: 3.3 In addition to copying the template, tokens within the template will be replaced with new values for the date token and title token. :param shapefile_path: Path to the shapefile that will have the flooded field added. :type shapefile_path: basestring """ source_xml_path = resources_path('petabencana', 'flood-keywords.xml') output_xml_path = shapefile_path.replace('shp', 'xml') LOGGER.info('Copying xml to: %s' % output_xml_path) title_token = '[TITLE]' new_title = self.tr('Jakarta Floods - %s' % self.time_stamp) date_token = '[DATE]' new_date = self.time_stamp with open(source_xml_path) as source_file, \ open(output_xml_path, 'w') as output_file: for line in source_file: line = line.replace(date_token, new_date) line = line.replace(title_token, new_title) output_file.write(line) @staticmethod def copy_style(shapefile_path): """Copy style from the OSM resource directory to the output path. .. versionadded: 3.3 :param shapefile_path: Path to the shapefile that should get the path added. :type shapefile_path: basestring """ source_qml_path = resources_path('petabencana', 'flood-style.qml') output_qml_path = shapefile_path.replace('shp', 'qml') LOGGER.info('Copying qml to: %s' % output_qml_path) copy(source_qml_path, output_qml_path) def get_output_base_path( self, output_directory, output_prefix, with_date_stamp, feature_type, overwrite): """Get a full base name path to save the shapefile. TODO: This is cut & paste from OSM - refactor to have one method :param output_directory: The directory where to put results. :type output_directory: str :param output_prefix: The prefix to add for the shapefile. :type output_prefix: str :param with_date_stamp: Whether to add a datestamp in between the file prefix and the feature_type for the shapefile name. :type output_prefix: str :param feature_type: What kind of data will be downloaded. Will be used for the shapefile name. :type feature_type: str :param overwrite: Boolean to know if we can overwrite existing files. :type overwrite: bool :return: The base path. :rtype: str """ if with_date_stamp and self.time_stamp is not None: time_stamp = self.time_stamp.replace(' ', '-') time_stamp = time_stamp.replace(':', '-') time_stamp += '-' feature_type = time_stamp + feature_type path = os.path.join( output_directory, '%s%s' % (output_prefix, feature_type)) if overwrite: # If a shapefile exists, we must remove it (only the .shp) shp = '%s.shp' % path if os.path.isfile(shp): os.remove(shp) else: separator = '-' suffix = self.get_unique_file_path_suffix( '%s.shp' % path, separator) if suffix: path = os.path.join(output_directory, '%s%s%s%s' % ( output_prefix, feature_type, separator, suffix)) return path @staticmethod def get_unique_file_path_suffix(file_path, separator='-', i=0): """Return the minimum number to suffix the file to not overwrite one. Example : /tmp/a.txt exists. - With file_path='/tmp/b.txt' will return 0. - With file_path='/tmp/a.txt' will return 1 (/tmp/a-1.txt) TODO: This is cut & paste from OSM - refactor to have one method :param file_path: The file to check. :type file_path: str :param separator: The separator to add before the prefix. :type separator: str :param i: The minimum prefix to check. :type i: int :return: The minimum prefix you should add to not overwrite a file. :rtype: int """ basename = os.path.splitext(file_path) if i != 0: file_path_test = os.path.join( '%s%s%s%s' % (basename[0], separator, i, basename[1])) else: file_path_test = file_path if os.path.isfile(file_path_test): return PetaBencanaDialog.get_unique_file_path_suffix( file_path, separator, i + 1) else: return i def require_directory(self): """Ensure directory path entered in dialog exist. When the path does not exist, this function will ask the user if he want to create it or not. TODO: This is cut & paste from OSM - refactor to have one method :raises: CanceledImportDialogError - when user choose 'No' in the question dialog for creating directory. """ path = self.output_directory.text() if os.path.exists(path): return title = self.tr('Directory %s not exist') % path question = self.tr( 'Directory %s not exist. Do you want to create it?') % path # noinspection PyCallByClass,PyTypeChecker answer = QMessageBox.question( self, title, question, QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.Yes: if len(path) != 0: os.makedirs(path) else: # noinspection PyCallByClass,PyTypeChecker,PyArgumentList display_warning_message_box( self, self.tr('InaSAFE error'), self.tr('Output directory can not be empty.')) raise CanceledImportDialogError() else: raise CanceledImportDialogError() def load_shapefile(self, feature_type, base_path): """Load downloaded shape file to QGIS Main Window. TODO: This is cut & paste from OSM - refactor to have one method :param feature_type: What kind of features should be downloaded. Currently 'buildings', 'building-points' or 'roads' are supported. :type feature_type: str :param base_path: The base path of the shape file (without extension). :type base_path: str :raises: FileMissingError - when buildings.shp not exist """ path = '%s.shp' % base_path if not os.path.exists(path): message = self.tr( '%s does not exist. The server does not have any data for ' 'this extent.' % path) raise FileMissingError(message) self.iface.addVectorLayer(path, feature_type, 'ogr') def reject(self): """Redefinition of the method. It will call the super method. """ super(PetaBencanaDialog, self).reject() def download(self, url, output_path): """Download file from API url and write to output path. :param url: URL of the API. :type url: str :param output_path: Path of output file, :type output_path: str """ request_failed_message = self.tr( "Can't access PetaBencana API: {source}").format( source=url) downloader = FileDownloader(url, output_path) result, message = downloader.download() if not result: display_warning_message_box( self, self.tr('Download error'), self.tr(request_failed_message + '\n' + message)) if result == QNetworkReply.OperationCanceledError: display_warning_message_box( self, self.tr('Download error'), self.tr(message)) # The function below might be usefull for future usage. # def get_available_area(self): # """Function to automatically get the available area on API. # *still cannot get string data from QByteArray* # """ # available_area = [] # network_manager = QgsNetworkAccessManager.instance() # api_url = QUrl('https://data.petabencana.id/cities') # api_request = QNetworkRequest(api_url) # api_response = network_manager.get(api_request) # data = api_response.readAll() # json_response = QScriptEngine().evaluate(data) # geometries = json_response.property('output').property('geometries') # iterator = QScriptValueIterator(geometries) # while iterator.hasNext(): # iterator.next() # geometry = iterator.value() # geometry_code = ( # geometry.property('properties').property('code').toString()) # available_area.append(geometry_code) def populate_combo_box(self): """Populate combobox for selecting city.""" if self.radio_button_production.isChecked(): self.source = production_api['url'] available_data = production_api['available_data'] else: self.source = development_api['url'] available_data = development_api['available_data'] self.city_combo_box.clear() for index, data in enumerate(available_data): self.city_combo_box.addItem(data['name']) self.city_combo_box.setItemData( index, data['code'], Qt.UserRole) def define_url(self): """Define API url based on which source is selected. :return: Valid url of selected source. :rtype: str """ current_index = self.city_combo_box.currentIndex() city_code = self.city_combo_box.itemData(current_index, Qt.UserRole) source = (self.source).format(city_code=city_code) return source
class DefaultValueParameterWidget(GenericParameterWidget): """Widget class for Default Value Parameter.""" def __init__(self, parameter, parent=None): """Constructor. :param parameter: A DefaultValueParameter object. :type parameter: DefaultValueParameter """ super(DefaultValueParameterWidget, self).__init__(parameter, parent) self.radio_button_layout = QHBoxLayout() # Create radio button group self.input_button_group = QButtonGroup() for i in range(len(self._parameter.labels)): if '%s' in self._parameter.labels[i]: label = ( self._parameter.labels[i] % self._parameter.options[i]) else: label = self._parameter.labels[i] radio_button = QRadioButton(label) self.radio_button_layout.addWidget(radio_button) self.input_button_group.addButton(radio_button, i) if self._parameter.value == \ self._parameter.options[i]: radio_button.setChecked(True) # Create double spin box for custom value self.custom_value = QDoubleSpinBox() self.custom_value.setSingleStep(0.1) if self._parameter.options[-1]: self.custom_value.setValue(self._parameter.options[-1]) self.radio_button_layout.addWidget(self.custom_value) self.toggle_custom_value() self.inner_input_layout.addLayout(self.radio_button_layout) # Connect # noinspection PyUnresolvedReferences self.input_button_group.buttonClicked.connect( self.toggle_custom_value) def raise_invalid_type_exception(self): """Raise invalid type.""" message = 'Expecting element type of %s' % ( self._parameter.element_type.__name__) err = ValueError(message) return err def get_parameter(self): """Obtain list parameter object from the current widget state. :returns: A DefaultValueParameter from the current state of widget :rtype: DefaultValueParameter """ radio_button_checked_id = self.input_button_group.checkedId() # No radio button checked, then default value = None if radio_button_checked_id == -1: self._parameter.value = None # The last radio button (custom) is checked, get the value from the # line edit elif radio_button_checked_id == len(self._parameter.options) - 1: self._parameter.options[radio_button_checked_id] = \ self.custom_value.value() self._parameter.value = self.custom_value.value() else: self._parameter.value = self._parameter.options[ radio_button_checked_id] return self._parameter def set_value(self, value): """Set value by item's string. :param value: The value. :type value: str, int :returns: True if success, else False. :rtype: bool """ # Find index of choice try: value_index = self._parameter.options.index(value) self.input_button_group.button(value_index).setChecked(True) except ValueError: last_index = len(self._parameter.options) - 1 self.input_button_group.button(last_index).setChecked( True) self.custom_value.setValue(value) self.toggle_custom_value() def toggle_custom_value(self): """Enable or disable the custom value line edit.""" radio_button_checked_id = self.input_button_group.checkedId() if (radio_button_checked_id == len(self._parameter.options) - 1): self.custom_value.setDisabled(False) else: self.custom_value.setDisabled(True)
def __init__(self, parameter, parent=None): """Constructor. :param parameter: A GroupSelectParameter object. :type parameter: GroupSelectParameter """ QWidget.__init__(self, parent) self._parameter = parameter # Store spin box self.spin_boxes = {} # Create elements # Label (name) self.label = QLabel(self._parameter.name) # Layouts self.main_layout = QVBoxLayout() self.input_layout = QVBoxLayout() # _inner_input_layout must be filled with widget in the child class self.inner_input_layout = QVBoxLayout() self.radio_button_layout = QGridLayout() # Create radio button group self.input_button_group = QButtonGroup() # List widget self.list_widget = QListWidget() self.list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection) self.list_widget.setDragDropMode(QAbstractItemView.DragDrop) self.list_widget.setDefaultDropAction(Qt.MoveAction) self.list_widget.setEnabled(False) self.list_widget.setSizePolicy( QSizePolicy.Maximum, QSizePolicy.Expanding) for i, key in enumerate(self._parameter.options): value = self._parameter.options[key] radio_button = QRadioButton(value.get('label')) self.radio_button_layout.addWidget(radio_button, i, 0) if value.get('type') == SINGLE_DYNAMIC: percentage_spin_box = PercentageSpinBox(self) self.radio_button_layout.addWidget(percentage_spin_box, i, 1) percentage_spin_box.setValue(value.get('value', 0)) step = percentage_spin_box.singleStep() if step > 1: precision = 0 else: precision = len(str(step).split('.')[1]) if precision > 3: precision = 3 percentage_spin_box.setDecimals(precision) self.spin_boxes[key] = percentage_spin_box # Enable spin box depends on the selected option if self._parameter.selected == key: percentage_spin_box.setEnabled(True) else: percentage_spin_box.setEnabled(False) elif value.get('type') == STATIC: static_value = value.get('value', 0) if static_value is not None: self.radio_button_layout.addWidget( QLabel(str(static_value * 100) + ' %'), i, 1) elif value.get('type') == MULTIPLE_DYNAMIC: if self._parameter.selected == key: self.list_widget.setEnabled(True) else: self.list_widget.setEnabled(False) self.input_button_group.addButton(radio_button, i) if self._parameter.selected == key: radio_button.setChecked(True) # Help text self.help_label = QLabel(self._parameter.help_text) self.help_label.setSizePolicy( QSizePolicy.Maximum, QSizePolicy.Expanding) self.help_label.setWordWrap(True) self.help_label.setAlignment(Qt.AlignTop) self.inner_input_layout.addLayout(self.radio_button_layout) self.inner_input_layout.addWidget(self.list_widget) # Put elements into layouts self.input_layout.addWidget(self.label) self.input_layout.addLayout(self.inner_input_layout) self.help_layout = QVBoxLayout() self.help_layout.addWidget(self.help_label) self.main_layout.addLayout(self.input_layout) self.main_layout.addLayout(self.help_layout) self.setLayout(self.main_layout) self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) # Update list widget self.update_list_widget() # Connect signal # noinspection PyUnresolvedReferences self.input_button_group.buttonClicked.connect( self.radio_buttons_clicked)
class GroupSelectParameterWidget(GenericParameterWidget): """Widget class for Group Select Parameter.""" def __init__(self, parameter, parent=None): """Constructor. :param parameter: A GroupSelectParameter object. :type parameter: GroupSelectParameter """ QWidget.__init__(self, parent) self._parameter = parameter # Store spin box self.spin_boxes = {} # Create elements # Label (name) self.label = QLabel(self._parameter.name) # Layouts self.main_layout = QVBoxLayout() self.input_layout = QVBoxLayout() # _inner_input_layout must be filled with widget in the child class self.inner_input_layout = QVBoxLayout() self.radio_button_layout = QGridLayout() # Create radio button group self.input_button_group = QButtonGroup() # List widget self.list_widget = QListWidget() self.list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection) self.list_widget.setDragDropMode(QAbstractItemView.DragDrop) self.list_widget.setDefaultDropAction(Qt.MoveAction) self.list_widget.setEnabled(False) self.list_widget.setSizePolicy( QSizePolicy.Maximum, QSizePolicy.Expanding) for i, key in enumerate(self._parameter.options): value = self._parameter.options[key] radio_button = QRadioButton(value.get('label')) self.radio_button_layout.addWidget(radio_button, i, 0) if value.get('type') == SINGLE_DYNAMIC: percentage_spin_box = PercentageSpinBox(self) self.radio_button_layout.addWidget(percentage_spin_box, i, 1) percentage_spin_box.setValue(value.get('value', 0)) step = percentage_spin_box.singleStep() if step > 1: precision = 0 else: precision = len(str(step).split('.')[1]) if precision > 3: precision = 3 percentage_spin_box.setDecimals(precision) self.spin_boxes[key] = percentage_spin_box # Enable spin box depends on the selected option if self._parameter.selected == key: percentage_spin_box.setEnabled(True) else: percentage_spin_box.setEnabled(False) elif value.get('type') == STATIC: static_value = value.get('value', 0) if static_value is not None: self.radio_button_layout.addWidget( QLabel(str(static_value * 100) + ' %'), i, 1) elif value.get('type') == MULTIPLE_DYNAMIC: if self._parameter.selected == key: self.list_widget.setEnabled(True) else: self.list_widget.setEnabled(False) self.input_button_group.addButton(radio_button, i) if self._parameter.selected == key: radio_button.setChecked(True) # Help text self.help_label = QLabel(self._parameter.help_text) self.help_label.setSizePolicy( QSizePolicy.Maximum, QSizePolicy.Expanding) self.help_label.setWordWrap(True) self.help_label.setAlignment(Qt.AlignTop) self.inner_input_layout.addLayout(self.radio_button_layout) self.inner_input_layout.addWidget(self.list_widget) # Put elements into layouts self.input_layout.addWidget(self.label) self.input_layout.addLayout(self.inner_input_layout) self.help_layout = QVBoxLayout() self.help_layout.addWidget(self.help_label) self.main_layout.addLayout(self.input_layout) self.main_layout.addLayout(self.help_layout) self.setLayout(self.main_layout) self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) # Update list widget self.update_list_widget() # Connect signal # noinspection PyUnresolvedReferences self.input_button_group.buttonClicked.connect( self.radio_buttons_clicked) def get_parameter(self): """Obtain list parameter object from the current widget state. :returns: A DefaultValueParameter from the current state of widget :rtype: DefaultValueParameter """ # Set value for each key for key, value in list(self._parameter.options.items()): if value.get('type') == STATIC: continue elif value.get('type') == SINGLE_DYNAMIC: new_value = self.spin_boxes.get(key).value() self._parameter.set_value_for_key(key, new_value) elif value.get('type') == MULTIPLE_DYNAMIC: # Need to iterate through all items items = [] for index in range(self.list_widget.count()): items.append(self.list_widget.item(index)) new_value = [i.text() for i in items] self._parameter.set_value_for_key(key, new_value) # Get selected radio button radio_button_checked_id = self.input_button_group.checkedId() # No radio button checked, then default value = None if radio_button_checked_id == -1: self._parameter.selected = None else: self._parameter.selected = list(self._parameter.options.keys())[ radio_button_checked_id] return self._parameter def update_list_widget(self): """Update list widget when radio button is clicked.""" # Get selected radio button radio_button_checked_id = self.input_button_group.checkedId() # No radio button checked, then default value = None if radio_button_checked_id > -1: selected_dict = list(self._parameter.options.values())[ radio_button_checked_id] if selected_dict.get('type') == MULTIPLE_DYNAMIC: for field in selected_dict.get('value'): # Update list widget field_item = QListWidgetItem(self.list_widget) field_item.setFlags( Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) field_item.setData(Qt.UserRole, field) field_item.setText(field) self.list_widget.addItem(field_item) def radio_buttons_clicked(self): """Handler when selected radio button changed.""" # Disable all spin boxes for spin_box in list(self.spin_boxes.values()): spin_box.setEnabled(False) # Disable list widget self.list_widget.setEnabled(False) # Get selected radio button radio_button_checked_id = self.input_button_group.checkedId() if radio_button_checked_id > -1: selected_value = list(self._parameter.options.values())[ radio_button_checked_id] if selected_value.get('type') == MULTIPLE_DYNAMIC: # Enable list widget self.list_widget.setEnabled(True) elif selected_value.get('type') == SINGLE_DYNAMIC: selected_key = list(self._parameter.options.keys())[ radio_button_checked_id] self.spin_boxes[selected_key].setEnabled(True) def select_radio_button(self, key): """Helper to select a radio button with key. :param key: The key of the radio button. :type key: str """ key_index = list(self._parameter.options.keys()).index(key) radio_button = self.input_button_group.button(key_index) radio_button.click()
class CheckboxesPanel(QWidget): def __init__(self, options, multiple, columns=2, parent=None): super(CheckboxesPanel, self).__init__(parent) self._options = [] for i, option in enumerate(options): if isinstance(option, str): self._options.append((i, option)) else: self.options.append(option) self._multiple = multiple self._buttons = [] rows = len(options) / columns self._buttonGroup = QButtonGroup() self._buttonGroup.setExclusive(not multiple) layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setMargin(0) for i, (v, t) in enumerate(self._options): if multiple: button = QCheckBox(t) else: button = QRadioButton(t) self._buttons.append((v, button)) self._buttonGroup.addButton(button, i) layout.addWidget(button, i % rows, i / rows) layout.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, columns) self.setLayout(layout) if multiple: self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showPopupMenu) def showPopupMenu(self): popup_menu = QMenu() select_all_action = QAction(self.tr('Select All'), popup_menu) select_all_action.triggered.connect(self.selectAll) clear_all_action = QAction(self.tr('Clear Selection'), popup_menu) clear_all_action.triggered.connect(self.deselectAll) popup_menu.addAction(select_all_action) popup_menu.addAction(clear_all_action) popup_menu.exec_(QCursor.pos()) def selectAll(self): for (v, button) in self._buttons: button.setChecked(True) def deselectAll(self): for (v, button) in self._buttons: button.setChecked(False) def value(self): if self._multiple: value = [] for (v, checkbox) in self._buttons: if checkbox.isChecked(): value.append(v) return value else: return self._options[self._buttonGroup.checkedId()][0] def setValue(self, value): if self._multiple: for (v, button) in self._buttons: if v in value: button.setChecked(True) else: for v, button in self._buttons: if v == value: button.setChecked(True)