Пример #1
0
    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)

        self.setWindowTitle(self.tr('InaSAFE OpenStreetMap Downloader'))

        self.iface = iface
        self.buildings_url = 'http://osm.linfiniti.com/buildings-shp'
        self.building_points_url = \
            'http://osm.linfiniti.com/building-points-shp'
        self.roads_url = 'http://osm.linfiniti.com/roads-shp'

        self.help_context = 'openstreetmap_downloader'
        # creating progress dialog for download
        self.progress_dialog = QProgressDialog(self)
        self.progress_dialog.setAutoClose(False)
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.progress_dialog.setWindowTitle(title)
        # Set up context help
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)

        self.show_info()

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)

        # Set Proxy in webpage
        proxy = get_proxy()
        self.network_manager = QNetworkAccessManager(self)
        if proxy is not None:
            self.network_manager.setProxy(proxy)
        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.button_extent_rectangle.clicked.connect(
            self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)
        self.update_extent_from_map_canvas()
    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)

        self.setWindowTitle(self.tr('InaSAFE OpenStreetMap Downloader'))

        self.iface = iface
        self.buildings_url = 'http://osm.linfiniti.com/buildings-shp'
        self.building_points_url = \
            'http://osm.linfiniti.com/building-points-shp'
        self.roads_url = 'http://osm.linfiniti.com/roads-shp'

        self.help_context = 'openstreetmap_downloader'
        # creating progress dialog for download
        self.progress_dialog = QProgressDialog(self)
        self.progress_dialog.setAutoClose(False)
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.progress_dialog.setWindowTitle(title)
        # Set up context help
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)

        self.show_info()

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)

        # Set Proxy in webpage
        proxy = get_proxy()
        self.network_manager = QNetworkAccessManager(self)
        if proxy is not None:
            self.network_manager.setProxy(proxy)
        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.button_extent_rectangle.clicked.connect(
            self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)
        self.update_extent_from_map_canvas()
Пример #3
0
    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)

        self.setWindowTitle(self.tr('InaSAFE OpenStreetMap Downloader'))

        self.iface = iface

        self.help_context = 'openstreetmap_downloader'
        # creating progress dialog for download
        self.progress_dialog = QProgressDialog(self)
        self.progress_dialog.setAutoClose(False)
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.progress_dialog.setWindowTitle(title)
        # Set up context help
        self.help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        # Allow toggling the help button
        self.help_button.setCheckable(True)
        self.help_button.toggled.connect(self.help_toggled)
        self.stacked_widget.setCurrentIndex(1)
        # Disable boundaries group box until boundary checkbox is ticked
        self.boundary_group.setEnabled(False)

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)

        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.capture_button.clicked.connect(
            self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)

        # Setup helper for admin_level
        json_file_path = resources_path('osm', 'admin_level_per_country.json')
        if os.path.isfile(json_file_path):
            self.countries = json.load(open(json_file_path))
            self.bbox_countries = None
            self.populate_countries()
            # connect
            self.country_comboBox.currentIndexChanged.connect(
                self.update_helper_political_level)
            self.admin_level_comboBox.currentIndexChanged.connect(
                self.update_helper_political_level)

        self.update_extent_from_map_canvas()
Пример #4
0
class OsmDownloaderDialog(QDialog, FORM_CLASS):
    """Downloader for OSM data."""

    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)

        self.setWindowTitle(self.tr('InaSAFE OpenStreetMap Downloader'))

        self.iface = iface

        self.help_context = 'openstreetmap_downloader'
        # creating progress dialog for download
        self.progress_dialog = QProgressDialog(self)
        self.progress_dialog.setAutoClose(False)
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.progress_dialog.setWindowTitle(title)
        # Set up context help
        self.help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        # Allow toggling the help button
        self.help_button.setCheckable(True)
        self.help_button.toggled.connect(self.help_toggled)
        self.stacked_widget.setCurrentIndex(1)
        # Disable boundaries group box until boundary checkbox is ticked
        self.boundary_group.setEnabled(False)

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)

        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.capture_button.clicked.connect(
            self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)

        # Setup helper for admin_level
        json_file_path = resources_path('osm', 'admin_level_per_country.json')
        if os.path.isfile(json_file_path):
            self.countries = json.load(open(json_file_path))
            self.bbox_countries = None
            self.populate_countries()
            # connect
            self.country_comboBox.currentIndexChanged.connect(
                self.update_helper_political_level)
            self.admin_level_comboBox.currentIndexChanged.connect(
                self.update_helper_political_level)

        self.update_extent_from_map_canvas()

    def update_helper_political_level(self):
        """To update the helper about the country and the admin_level."""
        current_country = self.country_comboBox.currentText()
        index = self.admin_level_comboBox.currentIndex()
        current_level = self.admin_level_comboBox.itemData(index)
        content = None
        try:
            content = \
                self.countries[current_country]['levels'][str(current_level)]
            if content == 'N/A' or content == 'fixme' or content == '':
                raise KeyError
        except KeyError:
            content = self.tr('undefined')
        finally:
            text = self.tr('which represents %s in') % (content)
            self.boundary_helper.setText(text)

    def populate_countries(self):
        """Populate the combobox about countries and levels."""
        for i in range(1, 12):
            self.admin_level_comboBox.addItem(self.tr("Level %s" % i), i)

        # Set current index to admin_level 8, the most common one
        self.admin_level_comboBox.setCurrentIndex(7)

        list_countries = self.countries.keys()
        list_countries.sort()
        for country in list_countries:
            self.country_comboBox.addItem(country)

        self.bbox_countries = {}
        for country in list_countries:
            coords = self.countries[country]['bbox']
            self.bbox_countries[country] = QgsRectangle(
                coords[0], coords[3], coords[2], coords[1])

        self.update_helper_political_level()

    @pyqtSlot()
    @pyqtSignature('bool')  # prevents actions being handled twice
    def help_toggled(self, flag):
        """Show or hide the help tab in the stacked widget.

        ..versionadded: 3.2

        :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.2
        """
        self.stacked_widget.setCurrentIndex(1)

    def show_help(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        self.stacked_widget.setCurrentIndex(0)
        header = html_header()
        footer = html_footer()

        string = header

        heading = m.Heading(self.tr('OSM Downloader'), **INFO_STYLE)
        body = self.tr(
            'This tool will fetch building (\'structure\') or road ('
            '\'highway\') data from the OpenStreetMap project for you. '
            'The downloaded data will have InaSAFE keywords defined and a '
            'default QGIS style applied. To use this tool effectively:'
        )
        tips = m.BulletedList()
        tips.add(self.tr(
            'Your current extent, when opening this window, will be used to '
            'determine the area for which you want data to be retrieved.'
            'You can interactively select the area by using the '
            '\'select on map\' button - which will temporarily hide this '
            'window and allow you to drag a rectangle on the map. After you '
            'have finished dragging the rectangle, this window will '
            'reappear.'))
        tips.add(self.tr(
            'Check the output directory is correct. Note that the saved '
            'dataset will be called either roads.shp or buildings.shp (and '
            'associated files).'
        ))
        tips.add(self.tr(
            'By default simple file names will be used (e.g. roads.shp, '
            'buildings.shp). If you wish you can specify a prefix to '
            'add in front of this default name. For example using a prefix '
            'of \'padang-\' will cause the downloaded files to be saved as '
            '\'padang-roads.shp\' and \'padang-buildings.shp\'. Note that '
            'the only allowed prefix characters are A-Z, a-z, 0-9 and the '
            'characters \'-\' and \'_\'. You can leave this blank if you '
            'prefer.'
        ))
        tips.add(self.tr(
            'If a dataset already exists in the output directory it will be '
            'overwritten.'
        ))
        tips.add(self.tr(
            'This tool requires a working internet connection and fetching '
            'buildings or roads will consume your bandwidth.'))
        tips.add(m.Link(
            'http://www.openstreetmap.org/copyright',
            text=self.tr(
                'Downloaded data is copyright OpenStreetMap contributors'
                ' (click for more info).')
        ))
        message = m.Message()
        message.add(m.Brand())
        message.add(heading)
        message.add(body)
        message.add(tips)
        string += message.to_html()
        string += footer

        self.web_view.setHtml(string)

    def restore_state(self):
        """ Read last state of GUI from configuration file."""
        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 """
        settings = QSettings()
        settings.setValue('directory', self.output_directory.text())

    def update_extent(self, extent):
        """Update extent value in GUI based from an extent.

        :param extent: A list in the form [xmin, ymin, xmax, ymax] where all
            coordinates provided are in Geographic / EPSG:4326.
        :type extent: list
        """
        self.x_minimum.setValue(extent[0])
        self.y_minimum.setValue(extent[1])
        self.x_maximum.setValue(extent[2])
        self.y_maximum.setValue(extent[3])

        # Updating the country if possible.
        rectangle = QgsRectangle(extent[0], extent[1], extent[2], extent[3])
        center = rectangle.center()
        for country in self.bbox_countries:
            if self.bbox_countries[country].contains(center):
                index = self.country_comboBox.findText(country)
                self.country_comboBox.setCurrentIndex(index)
                break
        else:
            self.country_comboBox.setCurrentIndex(0)

    def update_extent_from_map_canvas(self):
        """Update extent value in GUI based from value in map.

        .. note:: Delegates to update_extent()
        """

        self.bounding_box_group.setTitle(
            self.tr('Bounding box from the map canvas'))
        # Get the extent as [xmin, ymin, xmax, ymax]
        extent = viewport_geo_array(self.iface.mapCanvas())
        self.update_extent(extent)

    def update_extent_from_rectangle(self):
        """Update extent value in GUI based from the QgsMapTool rectangle.

        .. note:: Delegates to update_extent()
        """

        self.show()
        self.canvas.unsetMapTool(self.rectangle_map_tool)
        self.canvas.setMapTool(self.pan_tool)

        rectangle = self.rectangle_map_tool.rectangle()
        if rectangle:
            self.bounding_box_group.setTitle(
                self.tr('Bounding box from rectangle'))
            extent = rectangle_geo_array(rectangle, self.iface.mapCanvas())
            self.update_extent(extent)

    @pyqtSignature('')  # prevents actions being handled twice
    def on_directory_button_clicked(self):
        """Show a dialog to choose directory."""
        # noinspection PyCallByClass,PyTypeChecker
        self.output_directory.setText(QFileDialog.getExistingDirectory(
            self, self.tr('Select download directory')))

    def drag_rectangle_on_map_canvas(self):
        """Hide the dialog and allow the user to draw a rectangle."""

        self.hide()
        self.rectangle_map_tool.reset()
        self.canvas.unsetMapTool(self.pan_tool)
        self.canvas.setMapTool(self.rectangle_map_tool)

    def get_checked_features(self):
        """Create a tab with all checked features.

        :return A list with all features which are checked in the UI.
        :rtype list
        """
        feature_types = []
        if self.roads_flag.isChecked():
            feature_types.append('roads')
        if self.buildings_flag.isChecked():
            feature_types.append('buildings')
        if self.building_points_flag.isChecked():
            feature_types.append('building-points')
        if self.potential_idp_flag.isChecked():
            feature_types.append('potential-idp')
        if self.boundary_flag.isChecked():
            level = self.admin_level_comboBox.currentIndex() + 1
            feature_types.append('boundary-%s' % level)
        return feature_types

    def accept(self):
        """Do osm download and display it in QGIS."""
        error_dialog_title = self.tr('InaSAFE OpenStreetMap Downloader Error')

        # Lock the bounding_box_group
        self.bounding_box_group.setDisabled(True)

        # Get the extent
        y_minimum = self.y_minimum.value()
        y_maximum = self.y_maximum.value()
        x_minimum = self.x_minimum.value()
        x_maximum = self.x_maximum.value()
        extent = [x_minimum, y_minimum, x_maximum, y_maximum]

        # Validate extent
        valid_flag = validate_geo_array(extent)
        if not valid_flag:
            message = self.tr(
                'The bounding box is not valid. Please make sure it is '
                'valid or check your projection!')
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(self, error_dialog_title, message)
            # Unlock the bounding_box_group
            self.bounding_box_group.setEnabled(True)
            return

        # Validate features
        feature_types = self.get_checked_features()
        if len(feature_types) < 1:
            message = self.tr(
                'No feature selected. '
                'Please make sure you have checked one feature.')
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(self, error_dialog_title, message)
            # Unlock the bounding_box_group
            self.bounding_box_group.setEnabled(True)
            return

        try:
            self.save_state()
            self.require_directory()
            for feature_type in feature_types:

                output_directory = self.output_directory.text()
                output_prefix = self.filename_prefix.text()
                overwrite = self.overwrite_flag.isChecked()
                output_base_file_path = self.get_output_base_path(
                    output_directory, output_prefix, feature_type, overwrite)

                download(
                    feature_type,
                    output_base_file_path,
                    extent,
                    self.progress_dialog)

                try:
                    self.load_shapefile(feature_type, output_base_file_path)
                except FileMissingError as exception:
                    display_warning_message_box(
                        self,
                        error_dialog_title,
                        exception.message)
            self.done(QDialog.Accepted)
            self.rectangle_map_tool.reset()

        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as exception:  # pylint: disable=broad-except
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(
                self, error_dialog_title, exception.message)

            self.progress_dialog.cancel()

        finally:
            # Unlock the bounding_box_group
            self.bounding_box_group.setEnabled(True)

    def get_output_base_path(
            self,
            output_directory,
            output_prefix,
            feature_type,
            overwrite):
        """Get a full base name path to save the shapefile.

        :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 feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :param overwrite: Boolean to know if we can overwrite existing files.
        :type overwrite: bool

        :return: The base path.
        :rtype: str
        """
        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)

        :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 OsmDownloaderDialog.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.

        :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.

        :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')

        canvas_srid = self.canvas.mapRenderer().destinationCrs().srsid()
        on_the_fly_projection = self.canvas.hasCrsTransformEnabled()
        if canvas_srid != 4326 and not on_the_fly_projection:
            if QGis.QGIS_VERSION_INT >= 20400:
                self.canvas.setCrsTransformEnabled(True)
            else:
                display_warning_message_bar(
                    self.iface,
                    self.tr('Enable \'on the fly\''),
                    self.tr(
                        'Your current projection is different than EPSG:4326. '
                        'You should enable \'on the fly\' to display '
                        'correctly your layers')
                    )

    def reject(self):
        """Redefinition of the reject() method
        to remove the rectangle selection tool.
        It will call the super method.
        """

        self.canvas.unsetMapTool(self.rectangle_map_tool)
        self.rectangle_map_tool.reset()

        super(OsmDownloaderDialog, self).reject()
Пример #5
0
    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)
        icon = resources_path('img', 'icons', 'show-osm-download.svg')
        self.setWindowIcon(QtGui.QIcon(icon))
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.setWindowTitle(title)

        self.iface = iface
        self.progress_dialog = None

        # 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)

        # Output directory
        self.directory_button.clicked.connect(self.directory_button_clicked)
        self.output_directory.setPlaceholderText(
            self.tr('[Create a temporary layer]'))

        # Disable boundaries group box until boundary checkbox is ticked
        self.boundary_group.setEnabled(False)

        # 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)

        # Advanced panel
        self.line_edit_custom.setPlaceholderText(STAGING_SERVER)
        developer_mode = setting('developer_mode', expected_type=bool)
        self.group_box_advanced.setVisible(developer_mode)

        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.capture_button.clicked.connect(self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)

        # Setup helper for admin_level
        json_file_path = resources_path('osm', 'admin_level_per_country.json')
        if os.path.isfile(json_file_path):
            with open(json_file_path, encoding='utf-8') as f:
                self.countries = json.load(f)
                self.bbox_countries = None
                self.populate_countries()
                # connect
                self.country_comboBox.currentIndexChanged.connect(
                    self.update_helper_political_level)
                self.admin_level_comboBox.currentIndexChanged.connect(
                    self.update_helper_political_level)

        self.update_extent_from_map_canvas()
Пример #6
0
class OsmDownloaderDialog(QDialog, FORM_CLASS):
    """Downloader for OSM data."""
    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)
        icon = resources_path('img', 'icons', 'show-osm-download.svg')
        self.setWindowIcon(QtGui.QIcon(icon))
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.setWindowTitle(title)

        self.iface = iface
        self.progress_dialog = None

        # 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)

        # Output directory
        self.directory_button.clicked.connect(self.directory_button_clicked)
        self.output_directory.setPlaceholderText(
            self.tr('[Create a temporary layer]'))

        # Disable boundaries group box until boundary checkbox is ticked
        self.boundary_group.setEnabled(False)

        # 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)

        # Advanced panel
        self.line_edit_custom.setPlaceholderText(STAGING_SERVER)
        developer_mode = setting('developer_mode', expected_type=bool)
        self.group_box_advanced.setVisible(developer_mode)

        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.capture_button.clicked.connect(self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)

        # Setup helper for admin_level
        json_file_path = resources_path('osm', 'admin_level_per_country.json')
        if os.path.isfile(json_file_path):
            with open(json_file_path, encoding='utf-8') as f:
                self.countries = json.load(f)
                self.bbox_countries = None
                self.populate_countries()
                # connect
                self.country_comboBox.currentIndexChanged.connect(
                    self.update_helper_political_level)
                self.admin_level_comboBox.currentIndexChanged.connect(
                    self.update_helper_political_level)

        self.update_extent_from_map_canvas()

    def update_helper_political_level(self):
        """To update the helper about the country and the admin_level."""
        current_country = self.country_comboBox.currentText()
        index = self.admin_level_comboBox.currentIndex()
        current_level = self.admin_level_comboBox.itemData(index)
        content = None
        try:
            content = \
                self.countries[current_country]['levels'][str(current_level)]
            if content == 'N/A' or content == 'fixme' or content == '':
                raise KeyError
        except KeyError:
            content = self.tr('undefined')
        finally:
            text = self.tr('which represents %s in') % content
            self.boundary_helper.setText(text)

    def populate_countries(self):
        """Populate the combobox about countries and levels."""
        for i in range(1, 12):
            self.admin_level_comboBox.addItem(self.tr('Level %s') % i, i)

        # Set current index to admin_level 8, the most common one
        self.admin_level_comboBox.setCurrentIndex(7)

        list_countries = sorted(
            [self.tr(country) for country in list(self.countries.keys())])
        for country in list_countries:
            self.country_comboBox.addItem(country)

        self.bbox_countries = {}
        for country in list_countries:
            multipolygons = self.countries[country]['bbox']
            self.bbox_countries[country] = []
            for coords in multipolygons:
                bbox = QgsRectangle(coords[0], coords[3], coords[2], coords[1])
                self.bbox_countries[country].append(bbox)

        self.update_helper_political_level()

    def help_toggled(self, flag):
        """Show or hide the help tab in the stacked widget.

        .. versionadded: 3.2

        :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.2
        """
        self.main_stacked_widget.setCurrentIndex(1)

    def show_help(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        self.main_stacked_widget.setCurrentIndex(0)
        header = html_header()
        footer = html_footer()

        string = header

        message = osm_downloader_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."""
        last_path = setting('directory', '', expected_type=str)
        self.output_directory.setText(last_path)

    def save_state(self):
        """Store current state of GUI to configuration file."""
        set_setting('directory', self.output_directory.text())

    def update_extent(self, extent):
        """Update extent value in GUI based from an extent.

        :param extent: A list in the form [xmin, ymin, xmax, ymax] where all
            coordinates provided are in Geographic / EPSG:4326.
        :type extent: list
        """
        self.x_minimum.setValue(extent[0])
        self.y_minimum.setValue(extent[1])
        self.x_maximum.setValue(extent[2])
        self.y_maximum.setValue(extent[3])

        # Updating the country if possible.
        rectangle = QgsRectangle(extent[0], extent[1], extent[2], extent[3])
        center = rectangle.center()

        for country in self.bbox_countries:
            for polygon in self.bbox_countries[country]:
                if polygon.contains(center):
                    index = self.country_comboBox.findText(country)
                    self.country_comboBox.setCurrentIndex(index)
                    break
            else:
                # Continue if the inner loop wasn't broken.
                continue
            # Inner loop was broken, break the outer.
            break
        else:
            self.country_comboBox.setCurrentIndex(0)

    def update_extent_from_map_canvas(self):
        """Update extent value in GUI based from value in map.

        .. note:: Delegates to update_extent()
        """

        self.bounding_box_group.setTitle(
            self.tr('Bounding box from the map canvas'))
        # Get the extent as [xmin, ymin, xmax, ymax]
        extent = viewport_geo_array(self.iface.mapCanvas())
        self.update_extent(extent)

    def update_extent_from_rectangle(self):
        """Update extent value in GUI based from the QgsMapTool rectangle.

        .. note:: Delegates to update_extent()
        """

        self.show()
        self.canvas.unsetMapTool(self.rectangle_map_tool)
        self.canvas.setMapTool(self.pan_tool)

        rectangle = self.rectangle_map_tool.rectangle()
        if rectangle:
            self.bounding_box_group.setTitle(
                self.tr('Bounding box from rectangle'))
            extent = rectangle_geo_array(rectangle, self.iface.mapCanvas())
            self.update_extent(extent)

    def directory_button_clicked(self):
        """Show a dialog to choose directory."""
        # noinspection PyCallByClass,PyTypeChecker
        self.output_directory.setText(
            QFileDialog.getExistingDirectory(
                self, self.tr('Select download directory')))

    def drag_rectangle_on_map_canvas(self):
        """Hide the dialog and allow the user to draw a rectangle."""

        self.hide()
        self.rectangle_map_tool.reset()
        self.canvas.unsetMapTool(self.pan_tool)
        self.canvas.setMapTool(self.rectangle_map_tool)

    def get_checked_features(self):
        """Create a tab with all checked features.

        :return A list with all features which are checked in the UI.
        :rtype list
        """
        feature_types = []
        if self.roads_flag.isChecked():
            feature_types.append('roads')
        if self.buildings_flag.isChecked():
            feature_types.append('buildings')
        if self.building_points_flag.isChecked():
            feature_types.append('building-points')
        if self.flood_prone_flag.isChecked():
            feature_types.append('flood-prone')
        if self.evacuation_centers_flag.isChecked():
            feature_types.append('evacuation-centers')
        if self.boundary_flag.isChecked():
            level = self.admin_level_comboBox.currentIndex() + 1
            feature_types.append('boundary-%s' % level)
        return feature_types

    def accept(self):
        """Do osm download and display it in QGIS."""
        error_dialog_title = self.tr('InaSAFE OpenStreetMap Downloader Error')

        # Lock the bounding_box_group
        self.bounding_box_group.setDisabled(True)

        # Get the extent
        y_minimum = self.y_minimum.value()
        y_maximum = self.y_maximum.value()
        x_minimum = self.x_minimum.value()
        x_maximum = self.x_maximum.value()
        extent = [x_minimum, y_minimum, x_maximum, y_maximum]

        # Validate extent
        valid_flag = validate_geo_array(extent)
        if not valid_flag:
            message = self.tr(
                'The bounding box is not valid. Please make sure it is '
                'valid or check your projection!')
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(self, error_dialog_title, message)
            # Unlock the bounding_box_group
            self.bounding_box_group.setEnabled(True)
            return

        # Validate features
        feature_types = self.get_checked_features()
        if len(feature_types) < 1:
            message = self.tr('No feature selected. '
                              'Please make sure you have checked one feature.')
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(self, error_dialog_title, message)
            # Unlock the bounding_box_group
            self.bounding_box_group.setEnabled(True)
            return

        if self.radio_custom.isChecked():
            server_url = self.line_edit_custom.text()
            if not server_url:
                # It's the place holder.
                server_url = STAGING_SERVER
        else:
            server_url = PRODUCTION_SERVER

        try:
            self.save_state()
            self.require_directory()

            # creating progress dialog for download
            self.progress_dialog = QProgressDialog(self)
            self.progress_dialog.setAutoClose(False)
            self.progress_dialog.setWindowTitle(self.windowTitle())

            for feature_type in feature_types:

                output_directory = self.output_directory.text()
                if output_directory == '':
                    output_directory = temp_dir(sub_dir='work')
                output_prefix = self.filename_prefix.text()
                overwrite = self.overwrite_flag.isChecked()
                output_base_file_path = self.get_output_base_path(
                    output_directory, output_prefix, feature_type, overwrite)

                # noinspection PyTypeChecker
                download(feature_type, output_base_file_path, extent,
                         self.progress_dialog, server_url)

                try:
                    self.load_shapefile(feature_type, output_base_file_path)
                except FileMissingError as exception:
                    display_warning_message_box(self, error_dialog_title,
                                                str(exception))
            self.done(QDialog.Accepted)
            self.rectangle_map_tool.reset()

        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as exception:  # pylint: disable=broad-except
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(self, error_dialog_title,
                                        str(exception))

            self.progress_dialog.cancel()
            self.progress_dialog.deleteLater()

        finally:
            # Unlock the bounding_box_group
            self.bounding_box_group.setEnabled(True)

    def get_output_base_path(self, output_directory, output_prefix,
                             feature_type, overwrite):
        """Get a full base name path to save the shapefile.

        :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 feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :param overwrite: Boolean to know if we can overwrite existing files.
        :type overwrite: bool

        :return: The base path.
        :rtype: str
        """
        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)

        :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 OsmDownloaderDialog.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.

        :raises: CanceledImportDialogError - when user choose 'No' in
            the question dialog for creating directory.
        """
        path = self.output_directory.text()

        if path == '':
            # If let empty, we create an temporary directory
            return

        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.

        :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)

        layer = self.iface.addVectorLayer(path, feature_type, 'ogr')

        # Check if it's a building layer about the 2.5D
        if feature_type == 'buildings':
            layer_scope = QgsExpressionContextUtils.layerScope(layer)
            if not layer_scope.variable('qgis_25d_height'):
                QgsExpressionContextUtils.setLayerVariable(
                    layer, 'qgis_25d_height', 0.0002)
            if not layer_scope.variable('qgis_25d_angle'):
                QgsExpressionContextUtils.setLayerVariable(
                    layer, 'qgis_25d_angle', 70)

    def reject(self):
        """Redefinition of the method to remove the rectangle selection tool.

        It will call the super method.
        """
        self.canvas.unsetMapTool(self.rectangle_map_tool)
        self.rectangle_map_tool.reset()

        super(OsmDownloaderDialog, self).reject()
Пример #7
0
class OsmDownloaderDialog(QDialog, FORM_CLASS):
    """Downloader for OSM data."""
    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)

        self.setWindowTitle(self.tr('InaSAFE OpenStreetMap Downloader'))

        self.iface = iface
        self.buildings_url = 'http://osm.linfiniti.com/buildings-shp'
        self.building_points_url = \
            'http://osm.linfiniti.com/building-points-shp'
        self.roads_url = 'http://osm.linfiniti.com/roads-shp'

        self.help_context = 'openstreetmap_downloader'
        # creating progress dialog for download
        self.progress_dialog = QProgressDialog(self)
        self.progress_dialog.setAutoClose(False)
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.progress_dialog.setWindowTitle(title)
        # Set up context help
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)

        self.show_info()

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)

        # Set Proxy in webpage
        proxy = get_proxy()
        self.network_manager = QNetworkAccessManager(self)
        if proxy is not None:
            self.network_manager.setProxy(proxy)
        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.button_extent_rectangle.clicked.connect(
            self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)
        self.update_extent_from_map_canvas()

    def show_info(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        header = html_header()
        footer = html_footer()

        string = header

        heading = m.Heading(self.tr('OSM Downloader'), **INFO_STYLE)
        body = self.tr(
            'This tool will fetch building (\'structure\') or road ('
            '\'highway\') data from the OpenStreetMap project for you. '
            'The downloaded data will have InaSAFE keywords defined and a '
            'default QGIS style applied. To use this tool effectively:')
        tips = m.BulletedList()
        tips.add(
            self.
            tr('Your current extent, when opening this window, will be used to '
               'determine the area for which you want data to be retrieved.'
               'You can interactively select the area by using the '
               '\'select on map\' button - which will temporarily hide this '
               'window and allow you to drag a rectangle on the map. After you '
               'have finished dragging the rectangle, this window will '
               'reappear.'))
        tips.add(
            self.
            tr('Check the output directory is correct. Note that the saved '
               'dataset will be called either roads.shp or buildings.shp (and '
               'associated files).'))
        tips.add(
            self.
            tr('By default simple file names will be used (e.g. roads.shp, '
               'buildings.shp). If you wish you can specify a prefix to '
               'add in front of this default name. For example using a prefix '
               'of \'padang-\' will cause the downloaded files to be saved as '
               '\'padang-roads.shp\' and \'padang-buildings.shp\'. Note that '
               'the only allowed prefix characters are A-Z, a-z, 0-9 and the '
               'characters \'-\' and \'_\'. You can leave this blank if you '
               'prefer.'))
        tips.add(
            self.
            tr('If a dataset already exists in the output directory it will be '
               'overwritten.'))
        tips.add(
            self.
            tr('This tool requires a working internet connection and fetching '
               'buildings or roads will consume your bandwidth.'))
        tips.add(
            m.Link(
                'http://www.openstreetmap.org/copyright',
                text=self.tr(
                    'Downloaded data is copyright OpenStreetMap contributors'
                    ' (click for more info).')))
        message = m.Message()
        message.add(heading)
        message.add(body)
        message.add(tips)
        string += message.to_html()
        string += footer

        self.web_view.setHtml(string)

    def restore_state(self):
        """ Read last state of GUI from configuration file."""
        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 """
        settings = QSettings()
        settings.setValue('directory', self.output_directory.text())

    def show_help(self):
        """Load the help text for the dialog."""
        show_context_help(self.help_context)

    def update_extent(self, extent):
        """Update extent value in GUI based from an extent.

        :param extent: A list in the form [xmin, ymin, xmax, ymax] where all
            coordinates provided are in Geographic / EPSG:4326.
        :type extent: list
        """
        self.min_longitude.setText(str(extent[0]))
        self.min_latitude.setText(str(extent[1]))
        self.max_longitude.setText(str(extent[2]))
        self.max_latitude.setText(str(extent[3]))

    def update_extent_from_map_canvas(self):
        """Update extent value in GUI based from value in map.

        .. note:: Delegates to update_extent()
        """

        self.groupBox.setTitle(self.tr('Bounding box from the map canvas'))
        # Get the extent as [xmin, ymin, xmax, ymax]
        extent = viewport_geo_array(self.iface.mapCanvas())
        self.update_extent(extent)

    def update_extent_from_rectangle(self):
        """Update extent value in GUI based from the QgsMapTool rectangle.

        .. note:: Delegates to update_extent()
        """

        self.show()
        self.canvas.unsetMapTool(self.rectangle_map_tool)
        self.canvas.setMapTool(self.pan_tool)

        rectangle = self.rectangle_map_tool.rectangle()
        if rectangle:
            self.groupBox.setTitle(self.tr('Bounding box from rectangle'))
            extent = rectangle_geo_array(rectangle, self.iface.mapCanvas())
            self.update_extent(extent)

    def validate_extent(self):
        """Validate the bounding box before user click OK to download.

        :return: True if the bounding box is valid, otherwise False
        :rtype: bool
        """
        min_latitude = float(str(self.min_latitude.text()))
        max_latitude = float(str(self.max_latitude.text()))
        min_longitude = float(str(self.min_longitude.text()))
        max_longitude = float(str(self.max_longitude.text()))

        # min_latitude < max_latitude
        if min_latitude >= max_latitude:
            return False

        # min_longitude < max_longitude
        if min_longitude >= max_longitude:
            return False

        # -90 <= latitude <= 90
        if min_latitude < -90 or min_latitude > 90:
            return False
        if max_latitude < -90 or max_latitude > 90:
            return False

        # -180 <= longitude <= 180
        if min_longitude < -180 or min_longitude > 180:
            return False
        if max_longitude < -180 or max_longitude > 180:
            return False

        return True

    @pyqtSignature('')  # prevents actions being handled twice
    def on_directory_button_clicked(self):
        """Show a dialog to choose directory."""
        # noinspection PyCallByClass,PyTypeChecker
        self.output_directory.setText(
            QFileDialog.getExistingDirectory(
                self, self.tr('Select download directory')))

    def drag_rectangle_on_map_canvas(self):
        """Hide the dialog and allow the user to draw a rectangle."""

        self.hide()
        self.rectangle_map_tool.reset()
        self.canvas.unsetMapTool(self.pan_tool)
        self.canvas.setMapTool(self.rectangle_map_tool)

    def accept(self):
        """Do osm download and display it in QGIS."""
        error_dialog_title = self.tr('InaSAFE OpenStreetMap Downloader Error')

        # Lock the groupbox
        self.groupBox.setDisabled(True)

        # Validate extent
        valid_flag = self.validate_extent()
        if not valid_flag:
            message = self.tr(
                'The bounding box is not valid. Please make sure it is '
                'valid or check your projection!')
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(self, error_dialog_title, message)
            # Unlock the groupbox
            self.groupBox.setEnabled(True)
            return

        # Get all the feature types
        index = self.feature_type.currentIndex()
        if index == 0:
            feature_types = ['buildings', 'roads', 'building-points']
        elif index == 1:
            feature_types = ['buildings']
        elif index == 2:
            feature_types = ['building-points']
        else:
            feature_types = ['roads']

        try:
            self.save_state()
            self.require_directory()
            for feature_type in feature_types:

                output_directory = self.output_directory.text()
                output_prefix = self.filename_prefix.text()
                overwrite = self.overwrite_checkBox.isChecked()
                output_base_file_path = self.get_output_base_path(
                    output_directory, output_prefix, feature_type, overwrite)

                self.download(feature_type, output_base_file_path)
                try:
                    self.load_shapefile(feature_type, output_base_file_path)
                except FileMissingError as exception:
                    display_warning_message_box(self, error_dialog_title,
                                                exception.message)
            self.done(QDialog.Accepted)
            self.rectangle_map_tool.reset()

        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as exception:  # pylint: disable=broad-except
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(self, error_dialog_title,
                                        exception.message)

            self.progress_dialog.cancel()

        finally:
            # Unlock the groupbox
            self.groupBox.setEnabled(True)

    def get_output_base_path(self, output_directory, output_prefix,
                             feature_type, overwrite):
        """Get a full base name path to save the shapefile.

        :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 feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :param overwrite: Boolean to know if we can overwrite existing files.
        :type overwrite: bool

        :return: The base path.
        :rtype: str
        """
        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)

        :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 OsmDownloaderDialog.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.

        :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 download(self, feature_type, output_base_path):
        """Download shapefiles from Kartoza server.

        :param feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :param output_base_path: The base path of the shape file.
        :type output_base_path: str

        :raises: ImportDialogError, CanceledImportDialogError
        """

        # preparing necessary data
        min_longitude = str(self.min_longitude.text())
        min_latitude = str(self.min_latitude.text())
        max_longitude = str(self.max_longitude.text())
        max_latitude = str(self.max_latitude.text())

        box = ('{min_longitude},{min_latitude},{max_longitude},'
               '{max_latitude}').format(min_longitude=min_longitude,
                                        min_latitude=min_latitude,
                                        max_longitude=max_longitude,
                                        max_latitude=max_latitude)
        if feature_type == 'buildings':
            url = '{url}?bbox={box}&qgis_version=2'.format(
                url=self.buildings_url, box=box)
        elif feature_type == 'building-points':
            url = '{url}?bbox={box}&qgis_version=2'.format(
                url=self.building_points_url, box=box)
        else:
            url = '{url}?bbox={box}&qgis_version=2'.format(url=self.roads_url,
                                                           box=box)

        if 'LANG' in os.environ:
            env_lang = os.environ['LANG']
            url += '&lang=%s' % env_lang

        path = tempfile.mktemp('.shp.zip')

        # download and extract it
        self.fetch_zip(url, path, feature_type)
        self.extract_zip(path, output_base_path)

        self.progress_dialog.done(QDialog.Accepted)

    def fetch_zip(self, url, output_path, feature_type):
        """Download zip containing shp file and write to output_path.

        :param url: URL of the zip bundle.
        :type url: str

        :param output_path: Path of output file,
        :type output_path: str

        :param feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :raises: ImportDialogError - when network error occurred
        """
        LOGGER.debug('Downloading file from URL: %s' % url)
        LOGGER.debug('Downloading to: %s' % output_path)

        self.progress_dialog.show()

        # Infinite progress bar when the server is fetching data.
        # The progress bar will be updated with the file size later.
        self.progress_dialog.setMaximum(0)
        self.progress_dialog.setMinimum(0)
        self.progress_dialog.setValue(0)

        # Get a pretty label from feature_type, but not translatable
        label_feature_type = feature_type.replace('-', ' ')

        label_text = self.tr('Fetching %s' % label_feature_type)
        self.progress_dialog.setLabelText(label_text)

        # Download Process
        downloader = FileDownloader(self.network_manager, url, output_path,
                                    self.progress_dialog)
        try:
            result = downloader.download()
        except IOError as ex:
            raise IOError(ex)

        if result[0] is not True:
            _, error_message = result

            if result[0] == QNetworkReply.OperationCanceledError:
                raise CanceledImportDialogError(error_message)
            else:
                raise DownloadError(error_message)

    @staticmethod
    def extract_zip(zip_path, destination_base_path):
        """Extract different extensions to the destination base path.

        Example : test.zip contains a.shp, a.dbf, a.prj
        and destination_base_path = '/tmp/CT-buildings
        Expected result :
            - /tmp/CT-buildings.shp
            - /tmp/CT-buildings.dbf
            - /tmp/CT-buildings.prj

        If two files in the zip with the same extension, only one will be
        copied.

        :param zip_path: The path of the .zip file
        :type zip_path: str

        :param destination_base_path: The destination base path where the shp
            will be written to.
        :type destination_base_path: str

        :raises: IOError - when not able to open path or output_dir does not
            exist.
        """

        import zipfile

        handle = open(zip_path, 'rb')
        zip_file = zipfile.ZipFile(handle)
        for name in zip_file.namelist():
            extension = os.path.splitext(name)[1]
            output_final_path = u'%s%s' % (destination_base_path, extension)
            output_file = open(output_final_path, 'wb')
            output_file.write(zip_file.read(name))
            output_file.close()

        handle.close()

    def load_shapefile(self, feature_type, base_path):
        """Load downloaded shape file to QGIS Main Window.

        :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')

        canvas_srid = self.canvas.mapRenderer().destinationCrs().srsid()
        on_the_fly_projection = self.canvas.hasCrsTransformEnabled()
        if canvas_srid != 4326 and not on_the_fly_projection:
            if QGis.QGIS_VERSION_INT >= 20400:
                self.canvas.setCrsTransformEnabled(True)
            else:
                display_warning_message_bar(
                    self.tr('Enable \'on the fly\''),
                    self.tr(
                        'Your current projection is different than EPSG:4326. '
                        'You should enable \'on the fly\' to display '
                        'correctly your layers'))

    def reject(self):
        """Redefinition of the reject() method
        to remove the rectangle selection tool.
        It will call the super method.
        """

        self.canvas.unsetMapTool(self.rectangle_map_tool)
        self.rectangle_map_tool.reset()

        super(OsmDownloaderDialog, self).reject()
Пример #8
0
class ExtentSelectorDialog(QDialog, FORM_CLASS):
    """Dialog for letting user determine analysis extents."""

    extent_defined = pyqtSignal(QgsRectangle, QgsCoordinateReferenceSystem)
    clear_extent = pyqtSignal()
    extent_selector_closed = pyqtSignal()

    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog.
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: CRS for user defined analysis extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        icon = resources_path('img', 'icons', 'set-extents-tool.svg')
        self.setWindowIcon(QIcon(icon))

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:

            if isinstance(extent, QgsGeometry):
                # In InaSAFE V4, the extent is a QgsGeometry.
                # This like a hack to transform a geometry to a rectangle.
                extent = wkt_to_rectangle(extent.exportToWkt())

            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapSettings().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_button = self.button_box.button(QtGui.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)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

        # Populate the bookmarks list and connect the combobox
        self._populate_bookmarks_list()
        self.bookmarks_list.currentIndexChanged.connect(
            self.bookmarks_index_changed)

        # Reinstate the last used radio button
        mode = setting('analysis_extents_mode', HAZARD_EXPOSURE_VIEW)

        if mode == HAZARD_EXPOSURE_VIEW:
            self.hazard_exposure_view_extent.setChecked(True)
        elif mode == EXPOSURE:
            self.exposure_only.setChecked(True)
        elif mode == HAZARD_EXPOSURE:
            self.hazard_exposure_only.setChecked(True)
        elif mode == HAZARD_EXPOSURE_BOOKMARK:
            self.hazard_exposure_bookmark.setChecked(True)
        elif mode == HAZARD_EXPOSURE_BOUNDINGBOX:
            self.hazard_exposure_user_extent.setChecked(True)

        self.show_warnings.setChecked(
            setting('show_extent_warnings', True, bool))
        self.show_confirmations.setChecked(
            setting('show_extent_confirmations', True, bool))

    @pyqtSlot()
    @pyqtSignature('bool')  # prevents actions being handled twice
    def help_toggled(self, flag):
        """Show or hide the help tab in the main stacked widget.

        .. versionadded: 3.2.1

        :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.2.1
        """
        self.main_stacked_widget.setCurrentIndex(1)

    def show_help(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        self.main_stacked_widget.setCurrentIndex(0)
        header = html_header()
        footer = html_footer()

        string = header

        message = extent_selector_help()

        string += message.to_html()
        string += footer

        self.help_web_view.setHtml(string)

    def start_capture(self):
        """Start capturing the rectangle."""
        previous_map_tool = self.canvas.mapTool()
        if previous_map_tool != self.tool:
            self.previous_map_tool = previous_map_tool
        self.canvas.setMapTool(self.tool)
        self.hide()

    def stop_capture(self):
        """Stop capturing the rectangle and reshow the dialog."""
        self._populate_coordinates()
        self.canvas.setMapTool(self.previous_map_tool)
        self.show()

    def clear(self):
        """Clear the currently set extent."""
        self.tool.reset()
        self._populate_coordinates()
        # Revert to using hazard, exposure and view as basis for analysis
        self.hazard_exposure_view_extent.setChecked(True)

    def reject(self):
        """User rejected the rectangle."""
        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)
        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).reject()

    def accept(self):
        """User accepted the rectangle."""
        mode = None
        if self.hazard_exposure_view_extent.isChecked():
            mode = HAZARD_EXPOSURE_VIEW
        elif self.exposure_only.isChecked():
            mode = EXPOSURE
        elif self.hazard_exposure_only.isChecked():
            mode = HAZARD_EXPOSURE
        elif self.hazard_exposure_bookmark.isChecked():
            mode = HAZARD_EXPOSURE_BOOKMARK
        elif self.hazard_exposure_user_extent.isChecked():
            mode = HAZARD_EXPOSURE_BOUNDINGBOX

        set_setting('analysis_extents_mode', mode)

        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)

        if self.tool.rectangle() is not None:
            self.extent_defined.emit(
                self.tool.rectangle(),
                self.canvas.mapSettings().destinationCrs())
            extent = QgsGeometry.fromRect(self.tool.rectangle())
            LOGGER.info(
                'Requested extent : {wkt}'.format(wkt=extent.exportToWkt()))
        else:
            self.clear_extent.emit()

        # State handlers for showing warning message bars
        set_setting('show_extent_warnings', self.show_warnings.isChecked())
        set_setting('show_extent_confirmations',
                    self.show_confirmations.isChecked())

        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).accept()

    def _are_coordinates_valid(self):
        """Check if the coordinates are valid.

        :return: True if coordinates are valid otherwise False.
        :type: bool
        """
        try:
            QgsPoint(self.x_minimum.value(), self.y_maximum.value())
            QgsPoint(self.x_maximum.value(), self.y_minimum.value())
        except ValueError:
            return False

        return True

    def _coordinates_changed(self):
        """Handle a change in the coordinate input boxes."""
        if self._are_coordinates_valid():
            point1 = QgsPoint(self.x_minimum.value(), self.y_maximum.value())
            point2 = QgsPoint(self.x_maximum.value(), self.y_minimum.value())
            rect = QgsRectangle(point1, point2)

            self.tool.set_rectangle(rect)

    def _populate_coordinates(self):
        """Update the UI with the current active coordinates."""
        rect = self.tool.rectangle()
        self.blockSignals(True)
        if rect is not None:
            self.x_minimum.setValue(rect.xMinimum())
            self.y_minimum.setValue(rect.yMinimum())
            self.x_maximum.setValue(rect.xMaximum())
            self.y_maximum.setValue(rect.yMaximum())
        else:
            self.x_minimum.clear()
            self.y_minimum.clear()
            self.x_maximum.clear()
            self.y_maximum.clear()
        self.blockSignals(False)

    def bookmarks_index_changed(self):
        """Update the UI when the bookmarks combobox has changed."""
        index = self.bookmarks_list.currentIndex()
        if index >= 0:
            self.tool.reset()
            rectangle = self.bookmarks_list.itemData(index)
            self.tool.set_rectangle(rectangle)
            self.canvas.setExtent(rectangle)
            self.ok_button.setEnabled(True)
        else:
            self.ok_button.setDisabled(True)

    def on_hazard_exposure_view_extent_toggled(self, enabled):
        """Handler for hazard/exposure/view radiobutton toggle.

        :param enabled: The status of the radiobutton.
        :type enabled: bool
        """
        if enabled:
            self.tool.reset()
            self._populate_coordinates()

    def on_hazard_exposure_only_toggled(self, enabled):
        """Handler for hazard/exposure radiobutton toggle.

        :param enabled: The status of the radiobutton.
        :type enabled: bool
        """
        if enabled:
            self.tool.reset()
            self._populate_coordinates()

    def on_hazard_exposure_bookmark_toggled(self, enabled):
        """Update the UI when the user toggles the bookmarks radiobutton.

        :param enabled: The status of the radiobutton.
        :type enabled: bool
        """
        if enabled:
            self.bookmarks_index_changed()
        else:
            self.ok_button.setEnabled(True)
        self._populate_coordinates()

    def _populate_bookmarks_list(self):
        """Read the sqlite database and populate the bookmarks list.

        If no bookmarks are found, the bookmarks radio button will be disabled
        and the label will be shown indicating that the user should add
        bookmarks in QGIS first.

        Every bookmark are reprojected to mapcanvas crs.
        """
        # Connect to the QGIS sqlite database and check if the table exists.
        # noinspection PyArgumentList
        db_file_path = QgsApplication.qgisUserDbFilePath()
        db = sqlite3.connect(db_file_path)
        cursor = db.cursor()
        cursor.execute('SELECT COUNT(*) '
                       'FROM sqlite_master '
                       'WHERE type=\'table\' '
                       'AND name=\'tbl_bookmarks\';')

        number_of_rows = cursor.fetchone()[0]
        if number_of_rows > 0:
            cursor.execute('SELECT * ' 'FROM tbl_bookmarks;')
            bookmarks = cursor.fetchall()

            canvas_srid = self.canvas.mapSettings().destinationCrs().srsid()

            for bookmark in bookmarks:
                name = bookmark[1]
                srid = bookmark[7]
                rectangle = QgsRectangle(bookmark[3], bookmark[4], bookmark[5],
                                         bookmark[6])

                if srid != canvas_srid:
                    transform = QgsCoordinateTransform(srid, canvas_srid)
                    try:
                        rectangle = transform.transform(rectangle)
                    except QgsCsException:
                        rectangle = None

                if rectangle is None or rectangle.isEmpty():
                    pass

                self.bookmarks_list.addItem(name, rectangle)
        if self.bookmarks_list.currentIndex() >= 0:
            self.create_bookmarks_label.hide()
        else:
            self.create_bookmarks_label.show()
            self.hazard_exposure_bookmark.setDisabled(True)
            self.bookmarks_list.hide()
Пример #9
0
    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: Coordinate reference system for user defined analysis
            extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        self.show_info()
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None and crs is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:
            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapRenderer().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_context = 'user_extents'
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)
Пример #10
0
class ExtentSelectorDialog(QDialog, FORM_CLASS):
    """Dialog for letting user determine analysis extents.
    """

    extent_defined = pyqtSignal(QgsRectangle, QgsCoordinateReferenceSystem)
    clear_extent = pyqtSignal()
    extent_selector_closed = pyqtSignal()

    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: Coordinate reference system for user defined analysis
            extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        self.show_info()
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None and crs is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:
            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapRenderer().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_context = 'user_extents'
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

    def show_help(self):
        """Load the help text for the dialog."""
        show_context_help(self.help_context)

    def show_info(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        header = html_header()
        footer = html_footer()

        string = header

        heading = m.Heading(self.tr('User Extents Tool'), **INFO_STYLE)
        body = self.tr(
            'This tool allows you to specify exactly which geographical '
            'region should be used for your analysis. You can either '
            'enter the coordinates directly into the input boxes below '
            '(using the same CRS as the canvas is currently set to), or '
            'you can interactively select the area by using the \'select '
            'on map\' button - which will temporarily hide this window and '
            'allow you to drag a rectangle on the map. After you have '
            'finished dragging the rectangle, this window will reappear. '
            'If you enable the \'Toggle scenario outlines\' tool on the '
            'InaSAFE toolbar, your user defined extent will be shown on '
            'the map as a blue rectangle. Please note that when running '
            'your analysis, the effective analysis extent will be the '
            'intersection of the hazard extent, exposure extent and user '
            'extent - thus the entire user extent area may not be used for '
            'analysis.'

        )

        message = m.Message()
        message.add(heading)
        message.add(body)
        string += message.to_html()
        string += footer

        self.web_view.setHtml(string)

    def start_capture(self):
        """Start capturing the rectangle."""
        previous_map_tool = self.canvas.mapTool()
        if previous_map_tool != self.tool:
            self.previous_map_tool = previous_map_tool
        self.canvas.setMapTool(self.tool)
        self.hide()

    def stop_capture(self):
        """Stop capturing the rectangle and reshow the dialog."""
        self._populate_coordinates()
        self.canvas.setMapTool(self.previous_map_tool)
        self.show()

    def clear(self):
        """Clear the currently set extent.
        """
        self.tool.reset()
        self._populate_coordinates()

    def reject(self):
        """User rejected the rectangle.
        """
        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)
        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).reject()

    def accept(self):
        """User accepted the rectangle.
        """
        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)

        if self.tool.rectangle() is not None:
            LOGGER.info(
                'Extent selector setting user extents to %s' %
                self.tool.rectangle().toString())
            self.extent_defined.emit(
                self.tool.rectangle(),
                self.canvas.mapRenderer().destinationCrs()
            )
        else:
            LOGGER.info(
                'Extent selector setting user extents to nothing')
            self.clear_extent.emit()

        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).accept()

    def _are_coordinates_valid(self):
        """
        Check if the coordinates are valid.

        :return: True if coordinates are valid otherwise False.
        :type: bool
        """
        try:
            QgsPoint(
                self.x_minimum.value(),
                self.y_maximum.value())
            QgsPoint(
                self.x_maximum.value(),
                self.y_minimum.value())
        except ValueError:
            return False

        return True

    def _coordinates_changed(self):
        """
        Handle a change in the coordinate input boxes.
        """
        if self._are_coordinates_valid():
            point1 = QgsPoint(
                self.x_minimum.value(),
                self.y_maximum.value())
            point2 = QgsPoint(
                self.x_maximum.value(),
                self.y_minimum.value())
            rect = QgsRectangle(point1, point2)

            self.tool.set_rectangle(rect)

    def _populate_coordinates(self):
        """
        Update the UI with the current active coordinates.
        """
        rect = self.tool.rectangle()
        self.blockSignals(True)
        if rect is not None:
            self.x_minimum.setValue(rect.xMinimum())
            self.y_minimum.setValue(rect.yMinimum())
            self.x_maximum.setValue(rect.xMaximum())
            self.y_maximum.setValue(rect.yMaximum())
        else:
            self.x_minimum.clear()
            self.y_minimum.clear()
            self.x_maximum.clear()
            self.y_maximum.clear()
        self.blockSignals(False)
Пример #11
0
    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: Coordinate reference system for user defined analysis
            extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        self.show_info()
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None and crs is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:
            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapRenderer().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_context = 'user_extents'
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

        # Populate the bookmarks list and connect the combobox
        self._populate_bookmarks_list()
        self.comboBox_bookmarks_list.currentIndexChanged.connect(
            self.bookmarks_index_changed)
Пример #12
0
class ExtentSelectorDialog(QDialog, FORM_CLASS):
    """Dialog for letting user determine analysis extents.
    """

    extent_defined = pyqtSignal(QgsRectangle, QgsCoordinateReferenceSystem)
    clear_extent = pyqtSignal()
    extent_selector_closed = pyqtSignal()

    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: Coordinate reference system for user defined analysis
            extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        self.show_info()
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None and crs is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:
            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapRenderer().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_context = 'user_extents'
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

        # Populate the bookmarks list and connect the combobox
        self._populate_bookmarks_list()
        self.comboBox_bookmarks_list.currentIndexChanged.connect(
            self.bookmarks_index_changed)

    def show_help(self):
        """Load the help text for the dialog."""
        show_context_help(self.help_context)

    def show_info(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        header = html_header()
        footer = html_footer()

        string = header

        heading = m.Heading(self.tr('User Extents Tool'), **INFO_STYLE)
        body = self.tr(
            'This tool allows you to specify exactly which geographical '
            'region should be used for your analysis. You can either '
            'enter the coordinates directly into the input boxes below '
            '(using the same CRS as the canvas is currently set to), or '
            'you can interactively select the area by using the \'select '
            'on map\' button - which will temporarily hide this window and '
            'allow you to drag a rectangle on the map. After you have '
            'finished dragging the rectangle, this window will reappear. '
            'You can also use one of your bookmarks to set the region. '
            'If you enable the \'Toggle scenario outlines\' tool on the '
            'InaSAFE toolbar, your user defined extent will be shown on '
            'the map as a blue rectangle. Please note that when running '
            'your analysis, the effective analysis extent will be the '
            'intersection of the hazard extent, exposure extent and user '
            'extent - thus the entire user extent area may not be used for '
            'analysis.'

        )

        message = m.Message()
        message.add(heading)
        message.add(body)
        string += message.to_html()
        string += footer

        self.web_view.setHtml(string)

    def start_capture(self):
        """Start capturing the rectangle."""
        previous_map_tool = self.canvas.mapTool()
        if previous_map_tool != self.tool:
            self.previous_map_tool = previous_map_tool
        self.canvas.setMapTool(self.tool)
        self.hide()

    def stop_capture(self):
        """Stop capturing the rectangle and reshow the dialog."""
        self._populate_coordinates()
        self.canvas.setMapTool(self.previous_map_tool)
        self.show()

    def clear(self):
        """Clear the currently set extent.
        """
        self.tool.reset()
        self._populate_coordinates()

    def reject(self):
        """User rejected the rectangle.
        """
        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)
        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).reject()

    def accept(self):
        """User accepted the rectangle.
        """
        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)

        if self.tool.rectangle() is not None:
            LOGGER.info(
                'Extent selector setting user extents to %s' %
                self.tool.rectangle().toString())
            self.extent_defined.emit(
                self.tool.rectangle(),
                self.canvas.mapRenderer().destinationCrs()
            )
        else:
            LOGGER.info(
                'Extent selector setting user extents to nothing')
            self.clear_extent.emit()

        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).accept()

    def _are_coordinates_valid(self):
        """
        Check if the coordinates are valid.

        :return: True if coordinates are valid otherwise False.
        :type: bool
        """
        try:
            QgsPoint(
                self.x_minimum.value(),
                self.y_maximum.value())
            QgsPoint(
                self.x_maximum.value(),
                self.y_minimum.value())
        except ValueError:
            return False

        return True

    def _coordinates_changed(self):
        """
        Handle a change in the coordinate input boxes.
        """
        if self._are_coordinates_valid():
            point1 = QgsPoint(
                self.x_minimum.value(),
                self.y_maximum.value())
            point2 = QgsPoint(
                self.x_maximum.value(),
                self.y_minimum.value())
            rect = QgsRectangle(point1, point2)

            self.tool.set_rectangle(rect)

    def _populate_coordinates(self):
        """
        Update the UI with the current active coordinates.
        """
        rect = self.tool.rectangle()
        self.blockSignals(True)
        if rect is not None:
            self.x_minimum.setValue(rect.xMinimum())
            self.y_minimum.setValue(rect.yMinimum())
            self.x_maximum.setValue(rect.xMaximum())
            self.y_maximum.setValue(rect.yMaximum())
        else:
            self.x_minimum.clear()
            self.y_minimum.clear()
            self.x_maximum.clear()
            self.y_maximum.clear()
        self.blockSignals(False)

    def bookmarks_index_changed(self):
        """Update the UI when the bookmarks combobox has changed.
        """
        index = self.comboBox_bookmarks_list.currentIndex()
        if index >= 0:
            self.tool.reset()
            rectangle = self.comboBox_bookmarks_list.itemData(index)
            self.tool.set_rectangle(rectangle)
            self.canvas.setExtent(rectangle)
            self.ok_button.setEnabled(True)
        else:
            self.ok_button.setDisabled(True)

    def on_checkBox_use_bookmark_toggled(self, use_bookmark):
        """Update the UI when the user toggles the bookmarks checkbox.

        :param use_bookmark: The status of the checkbox.
        :type use_bookmark: bool
        """
        if use_bookmark:
            self.bookmarks_index_changed()
            self.groupBox_coordinates.setDisabled(True)
        else:
            self.groupBox_coordinates.setEnabled(True)
            self.ok_button.setEnabled(True)
        self._populate_coordinates()

    def _populate_bookmarks_list(self):
        """Read the sqlite database and populate the bookmarks list.

        Every bookmark are reprojected to mapcanvas crs.
        """

        # Connect to the QGIS sqlite database and check if the table exists.
        db_file_path = QgsApplication.qgisUserDbFilePath()
        if not isfile(db_file_path):
            # If the database does not exist.
            return

        try:
            db = sqlite3.connect(db_file_path)
            cursor = db.cursor()
            cursor.execute(
                'SELECT COUNT(*) '
                'FROM sqlite_master '
                'WHERE type=\'table\' '
                'AND name=\'tbl_bookmarks\';')

            number_of_rows = cursor.fetchone()[0]
            if number_of_rows > 0:
                cursor.execute(
                    'SELECT * '
                    'FROM tbl_bookmarks;')
                bookmarks = cursor.fetchall()

                map_renderer = self.canvas.mapRenderer()
                canvas_srid = map_renderer.destinationCrs().srsid()

                for bookmark in bookmarks:
                    name = bookmark[1]
                    srid = bookmark[7]
                    rectangle = QgsRectangle(
                        bookmark[3], bookmark[4], bookmark[5], bookmark[6])

                    if srid != canvas_srid:
                        transform = QgsCoordinateTransform(
                            srid, canvas_srid)
                        rectangle = transform.transform(rectangle)

                    if rectangle.isEmpty():
                        pass

                    self.comboBox_bookmarks_list.addItem(name, rectangle)
        except sqlite3.Error:
            # If we have any SQL error with SQLite.
            return
class ExtentSelectorDialog(QDialog, FORM_CLASS):
    """Dialog for letting user determine analysis extents.
    """

    extent_defined = pyqtSignal(QgsRectangle, QgsCoordinateReferenceSystem)
    clear_extent = pyqtSignal()
    extent_selector_closed = pyqtSignal()

    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: Coordinate reference system for user defined analysis
            extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        self.show_info()
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None and crs is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:
            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapRenderer().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_context = 'user_extents'
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

    def show_help(self):
        """Load the help text for the dialog."""
        show_context_help(self.help_context)

    def show_info(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        header = html_header()
        footer = html_footer()

        string = header

        heading = m.Heading(self.tr('User Extents Tool'), **INFO_STYLE)
        body = self.tr(
            'This tool allows you to specify exactly which geographical '
            'region should be used for your analysis. You can either '
            'enter the coordinates directly into the input boxes below '
            '(using the same CRS as the canvas is currently set to), or '
            'you can interactively select the area by using the \'select '
            'on map\' button - which will temporarily hide this window and '
            'allow you to drag a rectangle on the map. After you have '
            'finished dragging the rectangle, this window will reappear. '
            'If you enable the \'Toggle scenario outlines\' tool on the '
            'InaSAFE toolbar, your user defined extent will be shown on '
            'the map as a blue rectangle. Please note that when running '
            'your analysis, the effective analysis extent will be the '
            'intersection of the hazard extent, exposure extent and user '
            'extent - thus the entire user extent area may not be used for '
            'analysis.'

        )

        message = m.Message()
        message.add(heading)
        message.add(body)
        string += message.to_html()
        string += footer

        self.web_view.setHtml(string)

    def start_capture(self):
        """Start capturing the rectangle."""
        previous_map_tool = self.canvas.mapTool()
        if previous_map_tool != self.tool:
            self.previous_map_tool = previous_map_tool
        self.canvas.setMapTool(self.tool)
        self.hide()

    def stop_capture(self):
        """Stop capturing the rectangle and reshow the dialog."""
        self._populate_coordinates()
        self.canvas.setMapTool(self.previous_map_tool)
        self.show()

    def clear(self):
        """Clear the currently set extent.
        """
        self.tool.reset()
        self._populate_coordinates()

    def reject(self):
        """User rejected the rectangle.
        """
        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)
        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).reject()

    def accept(self):
        """User accepted the rectangle.
        """
        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)

        if self.tool.rectangle() is not None:
            LOGGER.info(
                'Extent selector setting user extents to %s' %
                self.tool.rectangle().toString())
            self.extent_defined.emit(
                self.tool.rectangle(),
                self.canvas.mapRenderer().destinationCrs()
            )
        else:
            LOGGER.info(
                'Extent selector setting user extents to nothing')
            self.clear_extent.emit()

        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).accept()

    def _are_coordinates_valid(self):
        """
        Check if the coordinates are valid.

        :return: True if coordinates are valid otherwise False.
        :type: bool
        """
        try:
            QgsPoint(
                self.x_minimum.value(),
                self.y_maximum.value())
            QgsPoint(
                self.x_maximum.value(),
                self.y_minimum.value())
        except ValueError:
            return False

        return True

    def _coordinates_changed(self):
        """
        Handle a change in the coordinate input boxes.
        """
        if self._are_coordinates_valid():
            point1 = QgsPoint(
                self.x_minimum.value(),
                self.y_maximum.value())
            point2 = QgsPoint(
                self.x_maximum.value(),
                self.y_minimum.value())
            rect = QgsRectangle(point1, point2)

            self.tool.set_rectangle(rect)

    def _populate_coordinates(self):
        """
        Update the UI with the current active coordinates.
        """
        rect = self.tool.rectangle()
        self.blockSignals(True)
        if rect is not None:
            self.x_minimum.setValue(rect.xMinimum())
            self.y_minimum.setValue(rect.yMinimum())
            self.x_maximum.setValue(rect.xMaximum())
            self.y_maximum.setValue(rect.yMaximum())
        else:
            self.x_minimum.clear()
            self.y_minimum.clear()
            self.x_maximum.clear()
            self.y_maximum.clear()
        self.blockSignals(False)
Пример #14
0
    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: Coordinate reference system for user defined analysis
            extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None and crs is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:
            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapRenderer().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_button = self.button_box.button(QtGui.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)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

        # Populate the bookmarks list and connect the combobox
        self._populate_bookmarks_list()
        self.bookmarks_list.currentIndexChanged.connect(
            self.bookmarks_index_changed)

        # Reinstate the last used radio button
        settings = QSettings()
        mode = settings.value(
            'inasafe/analysis_extents_mode',
            'HazardExposureView')
        if mode == 'HazardExposureView':
            self.hazard_exposure_view_extent.setChecked(True)
        elif mode == 'HazardExposure':
            self.hazard_exposure_only.setChecked(True)
        elif mode == 'HazardExposureBookmark':
            self.hazard_exposure_bookmark.setChecked(True)
        elif mode == 'HazardExposureBoundingBox':
            self.hazard_exposure_user_extent.setChecked(True)

        show_warnings = settings.value(
            'inasafe/show_extent_warnings',
            True,
            type=bool)
        if show_warnings:
            self.show_warnings.setChecked(True)
        else:
            self.show_warnings.setChecked(False)

        show_confirmations = settings.value(
            'inasafe/show_extent_confirmations',
            True,
            type=bool)
        if show_confirmations:
            self.show_confirmations.setChecked(True)
        else:
            self.show_confirmations.setChecked(False)
class ExtentSelectorDialog(QDialog, FORM_CLASS):
    """Dialog for letting user determine analysis extents.
    """

    extent_defined = pyqtSignal(QgsRectangle, QgsCoordinateReferenceSystem)
    clear_extent = pyqtSignal()
    extent_selector_closed = pyqtSignal()

    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: Coordinate reference system for user defined analysis
            extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None and crs is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:
            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapRenderer().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_button = self.button_box.button(QtGui.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)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

        # Populate the bookmarks list and connect the combobox
        self._populate_bookmarks_list()
        self.bookmarks_list.currentIndexChanged.connect(
            self.bookmarks_index_changed)

        # Reinstate the last used radio button
        settings = QSettings()
        mode = settings.value(
            'inasafe/analysis_extents_mode',
            'HazardExposureView')
        if mode == 'HazardExposureView':
            self.hazard_exposure_view_extent.setChecked(True)
        elif mode == 'HazardExposure':
            self.hazard_exposure_only.setChecked(True)
        elif mode == 'HazardExposureBookmark':
            self.hazard_exposure_bookmark.setChecked(True)
        elif mode == 'HazardExposureBoundingBox':
            self.hazard_exposure_user_extent.setChecked(True)

        show_warnings = settings.value(
            'inasafe/show_extent_warnings',
            True,
            type=bool)
        if show_warnings:
            self.show_warnings.setChecked(True)
        else:
            self.show_warnings.setChecked(False)

        show_confirmations = settings.value(
            'inasafe/show_extent_confirmations',
            True,
            type=bool)
        if show_confirmations:
            self.show_confirmations.setChecked(True)
        else:
            self.show_confirmations.setChecked(False)

    @pyqtSlot()
    @pyqtSignature('bool')  # prevents actions being handled twice
    def help_toggled(self, flag):
        """Show or hide the help tab in the main stacked widget.

        .. versionadded: 3.2.1

        :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.2.1
        """
        self.main_stacked_widget.setCurrentIndex(1)

    def show_help(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        self.main_stacked_widget.setCurrentIndex(0)
        header = html_header()
        footer = html_footer()

        string = header

        message = extent_selector_help()

        string += message.to_html()
        string += footer

        self.help_web_view.setHtml(string)

    def start_capture(self):
        """Start capturing the rectangle."""
        previous_map_tool = self.canvas.mapTool()
        if previous_map_tool != self.tool:
            self.previous_map_tool = previous_map_tool
        self.canvas.setMapTool(self.tool)
        self.hide()

    def stop_capture(self):
        """Stop capturing the rectangle and reshow the dialog."""
        self._populate_coordinates()
        self.canvas.setMapTool(self.previous_map_tool)
        self.show()

    def clear(self):
        """Clear the currently set extent.
        """
        self.tool.reset()
        self._populate_coordinates()
        # Revert to using hazard, exposure and view as basis for analysis
        self.hazard_exposure_view_extent.setChecked(True)

    def reject(self):
        """User rejected the rectangle.
        """
        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)
        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).reject()

    def accept(self):
        """User accepted the rectangle.
        """
        mode = None
        if self.hazard_exposure_view_extent.isChecked():
            mode = 'HazardExposureView'
        elif self.hazard_exposure_only.isChecked():
            mode = 'HazardExposure'
        elif self.hazard_exposure_bookmark.isChecked():
            mode = 'HazardExposureBookmark'
        elif self.hazard_exposure_user_extent.isChecked():
            mode = 'HazardExposureBoundingBox'

        # LOGGER.info(
        #    'Setting analysis extent mode to %s' % mode
        # )
        settings = QSettings()
        settings.setValue('inasafe/analysis_extents_mode', mode)

        self.canvas.unsetMapTool(self.tool)
        if self.previous_map_tool != self.tool:
            self.canvas.setMapTool(self.previous_map_tool)

        if self.tool.rectangle() is not None:
            # LOGGER.info(
            #     'Extent selector setting user extents to %s' %
            #     self.tool.rectangle().toString())
            self.extent_defined.emit(
                self.tool.rectangle(),
                self.canvas.mapRenderer().destinationCrs()
            )
        else:
            # LOGGER.info(
            #     'Extent selector setting user extents to nothing')
            self.clear_extent.emit()

        # State handlers for showing warning message bars
        settings.setValue(
            'inasafe/show_extent_warnings',
            self.show_warnings.isChecked())
        settings.setValue(
            'inasafe/show_extent_confirmations',
            self.show_confirmations.isChecked())

        self.tool.reset()
        self.extent_selector_closed.emit()
        super(ExtentSelectorDialog, self).accept()

    def _are_coordinates_valid(self):
        """
        Check if the coordinates are valid.

        :return: True if coordinates are valid otherwise False.
        :type: bool
        """
        try:
            QgsPoint(
                self.x_minimum.value(),
                self.y_maximum.value())
            QgsPoint(
                self.x_maximum.value(),
                self.y_minimum.value())
        except ValueError:
            return False

        return True

    def _coordinates_changed(self):
        """
        Handle a change in the coordinate input boxes.
        """
        if self._are_coordinates_valid():
            point1 = QgsPoint(
                self.x_minimum.value(),
                self.y_maximum.value())
            point2 = QgsPoint(
                self.x_maximum.value(),
                self.y_minimum.value())
            rect = QgsRectangle(point1, point2)

            self.tool.set_rectangle(rect)

    def _populate_coordinates(self):
        """
        Update the UI with the current active coordinates.
        """
        rect = self.tool.rectangle()
        self.blockSignals(True)
        if rect is not None:
            self.x_minimum.setValue(rect.xMinimum())
            self.y_minimum.setValue(rect.yMinimum())
            self.x_maximum.setValue(rect.xMaximum())
            self.y_maximum.setValue(rect.yMaximum())
        else:
            self.x_minimum.clear()
            self.y_minimum.clear()
            self.x_maximum.clear()
            self.y_maximum.clear()
        self.blockSignals(False)

    def bookmarks_index_changed(self):
        """Update the UI when the bookmarks combobox has changed.
        """
        index = self.bookmarks_list.currentIndex()
        if index >= 0:
            self.tool.reset()
            rectangle = self.bookmarks_list.itemData(index)
            self.tool.set_rectangle(rectangle)
            self.canvas.setExtent(rectangle)
            self.ok_button.setEnabled(True)
        else:
            self.ok_button.setDisabled(True)

    def on_hazard_exposure_view_extent_toggled(self, enabled):
        """Handler for hazard/exposure/view radiobutton toggle.

        :param enabled: The status of the radiobutton.
        :type enabled: bool
        """
        if enabled:
            self.tool.reset()
            self._populate_coordinates()

    def on_hazard_exposure_only_toggled(self, enabled):
        """Handler for hazard/exposure radiobutton toggle.

        :param enabled: The status of the radiobutton.
        :type enabled: bool
        """
        if enabled:
            self.tool.reset()
            self._populate_coordinates()

    def on_hazard_exposure_bookmark_toggled(self, enabled):
        """Update the UI when the user toggles the bookmarks radiobutton.

        :param enabled: The status of the radiobutton.
        :type enabled: bool
        """
        if enabled:
            self.bookmarks_index_changed()
        else:
            self.ok_button.setEnabled(True)
        self._populate_coordinates()

    def _populate_bookmarks_list(self):
        """Read the sqlite database and populate the bookmarks list.

        If no bookmarks are found, the bookmarks radio button will be disabled
        and the label will be shown indicating that the user should add
        bookmarks in QGIS first.

        Every bookmark are reprojected to mapcanvas crs.
        """
        # Connect to the QGIS sqlite database and check if the table exists.
        # noinspection PyArgumentList
        db_file_path = QgsApplication.qgisUserDbFilePath()
        db = sqlite3.connect(db_file_path)
        cursor = db.cursor()
        cursor.execute(
            'SELECT COUNT(*) '
            'FROM sqlite_master '
            'WHERE type=\'table\' '
            'AND name=\'tbl_bookmarks\';')

        number_of_rows = cursor.fetchone()[0]
        if number_of_rows > 0:
            cursor.execute(
                'SELECT * '
                'FROM tbl_bookmarks;')
            bookmarks = cursor.fetchall()

            canvas_srid = self.canvas.mapRenderer().destinationCrs().srsid()

            for bookmark in bookmarks:
                name = bookmark[1]
                srid = bookmark[7]
                rectangle = QgsRectangle(
                    bookmark[3], bookmark[4], bookmark[5], bookmark[6])

                if srid != canvas_srid:
                    transform = QgsCoordinateTransform(
                        srid, canvas_srid)
                    rectangle = transform.transform(rectangle)

                if rectangle.isEmpty():
                    pass

                self.bookmarks_list.addItem(name, rectangle)
        if self.bookmarks_list.currentIndex() >= 0:
            self.create_bookmarks_label.hide()
        else:
            self.create_bookmarks_label.show()
            self.hazard_exposure_bookmark.setDisabled(True)
            self.bookmarks_list.hide()
Пример #16
0
    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)

        self.setWindowTitle(self.tr('InaSAFE OpenStreetMap Downloader'))

        self.iface = iface

        self.help_context = 'openstreetmap_downloader'
        # creating progress dialog for download
        self.progress_dialog = QProgressDialog(self)
        self.progress_dialog.setAutoClose(False)
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.progress_dialog.setWindowTitle(title)

        # Set up things for context help
        self.help_button = self.button_box.button(QtGui.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)

        # Disable boundaries group box until boundary checkbox is ticked
        self.boundary_group.setEnabled(False)

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)

        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.capture_button.clicked.connect(self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)

        # Setup helper for admin_level
        json_file_path = resources_path('osm', 'admin_level_per_country.json')
        if os.path.isfile(json_file_path):
            self.countries = json.load(open(json_file_path))
            self.bbox_countries = None
            self.populate_countries()
            # connect
            self.country_comboBox.currentIndexChanged.connect(
                self.update_helper_political_level)
            self.admin_level_comboBox.currentIndexChanged.connect(
                self.update_helper_political_level)

        self.update_extent_from_map_canvas()
    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: Coordinate reference system for user defined analysis
            extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None and crs is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:
            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapRenderer().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_button = self.button_box.button(QtGui.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)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

        # Populate the bookmarks list and connect the combobox
        self._populate_bookmarks_list()
        self.bookmarks_list.currentIndexChanged.connect(
            self.bookmarks_index_changed)

        # Reinstate the last used radio button
        settings = QSettings()
        mode = settings.value(
            'inasafe/analysis_extents_mode',
            'HazardExposureView')
        if mode == 'HazardExposureView':
            self.hazard_exposure_view_extent.setChecked(True)
        elif mode == 'HazardExposure':
            self.hazard_exposure_only.setChecked(True)
        elif mode == 'HazardExposureBookmark':
            self.hazard_exposure_bookmark.setChecked(True)
        elif mode == 'HazardExposureBoundingBox':
            self.hazard_exposure_user_extent.setChecked(True)

        show_warnings = settings.value(
            'inasafe/show_extent_warnings',
            True,
            type=bool)
        if show_warnings:
            self.show_warnings.setChecked(True)
        else:
            self.show_warnings.setChecked(False)

        show_confirmations = settings.value(
            'inasafe/show_extent_confirmations',
            True,
            type=bool)
        if show_confirmations:
            self.show_confirmations.setChecked(True)
        else:
            self.show_confirmations.setChecked(False)
class OsmDownloaderDialog(QDialog, FORM_CLASS):
    """Downloader for OSM data."""

    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        :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)

        self.setWindowTitle(self.tr('InaSAFE OpenStreetMap Downloader'))

        self.iface = iface
        self.buildings_url = 'http://osm.linfiniti.com/buildings-shp'
        self.building_points_url = \
            'http://osm.linfiniti.com/building-points-shp'
        self.roads_url = 'http://osm.linfiniti.com/roads-shp'

        self.help_context = 'openstreetmap_downloader'
        # creating progress dialog for download
        self.progress_dialog = QProgressDialog(self)
        self.progress_dialog.setAutoClose(False)
        title = self.tr('InaSAFE OpenStreetMap Downloader')
        self.progress_dialog.setWindowTitle(title)
        # Set up context help
        help_button = self.button_box.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)

        self.show_info()

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)

        # Set Proxy in webpage
        proxy = get_proxy()
        self.network_manager = QNetworkAccessManager(self)
        if proxy is not None:
            self.network_manager.setProxy(proxy)
        self.restore_state()

        # Setup the rectangle map tool
        self.canvas = iface.mapCanvas()
        self.rectangle_map_tool = \
            RectangleMapTool(self.canvas)
        self.rectangle_map_tool.rectangle_created.connect(
            self.update_extent_from_rectangle)
        self.button_extent_rectangle.clicked.connect(
            self.drag_rectangle_on_map_canvas)

        # Setup pan tool
        self.pan_tool = QgsMapToolPan(self.canvas)
        self.canvas.setMapTool(self.pan_tool)
        self.update_extent_from_map_canvas()

    def show_info(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        header = html_header()
        footer = html_footer()

        string = header

        heading = m.Heading(self.tr('OSM Downloader'), **INFO_STYLE)
        body = self.tr(
            'This tool will fetch building (\'structure\') or road ('
            '\'highway\') data from the OpenStreetMap project for you. '
            'The downloaded data will have InaSAFE keywords defined and a '
            'default QGIS style applied. To use this tool effectively:'
        )
        tips = m.BulletedList()
        tips.add(self.tr(
            'Your current extent, when opening this window, will be used to '
            'determine the area for which you want data to be retrieved.'
            'You can interactively select the area by using the '
            '\'select on map\' button - which will temporarily hide this '
            'window and allow you to drag a rectangle on the map. After you '
            'have finished dragging the rectangle, this window will '
            'reappear.'))
        tips.add(self.tr(
            'Check the output directory is correct. Note that the saved '
            'dataset will be called either roads.shp or buildings.shp (and '
            'associated files).'
        ))
        tips.add(self.tr(
            'By default simple file names will be used (e.g. roads.shp, '
            'buildings.shp). If you wish you can specify a prefix to '
            'add in front of this default name. For example using a prefix '
            'of \'padang-\' will cause the downloaded files to be saved as '
            '\'padang-roads.shp\' and \'padang-buildings.shp\'. Note that '
            'the only allowed prefix characters are A-Z, a-z, 0-9 and the '
            'characters \'-\' and \'_\'. You can leave this blank if you '
            'prefer.'
        ))
        tips.add(self.tr(
            'If a dataset already exists in the output directory it will be '
            'overwritten.'
        ))
        tips.add(self.tr(
            'This tool requires a working internet connection and fetching '
            'buildings or roads will consume your bandwidth.'))
        tips.add(m.Link(
            'http://www.openstreetmap.org/copyright',
            text=self.tr(
                'Downloaded data is copyright OpenStreetMap contributors'
                ' (click for more info).')
        ))
        message = m.Message()
        message.add(heading)
        message.add(body)
        message.add(tips)
        string += message.to_html()
        string += footer

        self.web_view.setHtml(string)

    def restore_state(self):
        """ Read last state of GUI from configuration file."""
        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 """
        settings = QSettings()
        settings.setValue('directory', self.output_directory.text())

    def show_help(self):
        """Load the help text for the dialog."""
        show_context_help(self.help_context)

    def update_extent(self, extent):
        """Update extent value in GUI based from an extent.

        :param extent: A list in the form [xmin, ymin, xmax, ymax] where all
            coordinates provided are in Geographic / EPSG:4326.
        :type extent: list
        """
        self.min_longitude.setText(str(extent[0]))
        self.min_latitude.setText(str(extent[1]))
        self.max_longitude.setText(str(extent[2]))
        self.max_latitude.setText(str(extent[3]))

    def update_extent_from_map_canvas(self):
        """Update extent value in GUI based from value in map.

        .. note:: Delegates to update_extent()
        """

        self.groupBox.setTitle(self.tr('Bounding box from the map canvas'))
        # Get the extent as [xmin, ymin, xmax, ymax]
        extent = viewport_geo_array(self.iface.mapCanvas())
        self.update_extent(extent)

    def update_extent_from_rectangle(self):
        """Update extent value in GUI based from the QgsMapTool rectangle.

        .. note:: Delegates to update_extent()
        """

        self.show()
        self.canvas.unsetMapTool(self.rectangle_map_tool)
        self.canvas.setMapTool(self.pan_tool)

        rectangle = self.rectangle_map_tool.rectangle()
        if rectangle:
            self.groupBox.setTitle(self.tr('Bounding box from rectangle'))
            extent = rectangle_geo_array(rectangle, self.iface.mapCanvas())
            self.update_extent(extent)

    def validate_extent(self):
        """Validate the bounding box before user click OK to download.

        :return: True if the bounding box is valid, otherwise False
        :rtype: bool
        """
        min_latitude = float(str(self.min_latitude.text()))
        max_latitude = float(str(self.max_latitude.text()))
        min_longitude = float(str(self.min_longitude.text()))
        max_longitude = float(str(self.max_longitude.text()))

        # min_latitude < max_latitude
        if min_latitude >= max_latitude:
            return False

        # min_longitude < max_longitude
        if min_longitude >= max_longitude:
            return False

        # -90 <= latitude <= 90
        if min_latitude < -90 or min_latitude > 90:
            return False
        if max_latitude < -90 or max_latitude > 90:
            return False

        # -180 <= longitude <= 180
        if min_longitude < -180 or min_longitude > 180:
            return False
        if max_longitude < -180 or max_longitude > 180:
            return False

        return True

    @pyqtSignature('')  # prevents actions being handled twice
    def on_directory_button_clicked(self):
        """Show a dialog to choose directory."""
        # noinspection PyCallByClass,PyTypeChecker
        self.output_directory.setText(QFileDialog.getExistingDirectory(
            self, self.tr('Select download directory')))

    def drag_rectangle_on_map_canvas(self):
        """Hide the dialog and allow the user to draw a rectangle."""

        self.hide()
        self.rectangle_map_tool.reset()
        self.canvas.unsetMapTool(self.pan_tool)
        self.canvas.setMapTool(self.rectangle_map_tool)

    def accept(self):
        """Do osm download and display it in QGIS."""
        error_dialog_title = self.tr('InaSAFE OpenStreetMap Downloader Error')

        # Lock the groupbox
        self.groupBox.setDisabled(True)

        # Validate extent
        valid_flag = self.validate_extent()
        if not valid_flag:
            message = self.tr(
                'The bounding box is not valid. Please make sure it is '
                'valid or check your projection!')
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(self, error_dialog_title, message)
            # Unlock the groupbox
            self.groupBox.setEnabled(True)
            return

        # Get all the feature types
        index = self.feature_type.currentIndex()
        if index == 0:
            feature_types = ['buildings', 'roads', 'building-points']
        elif index == 1:
            feature_types = ['buildings']
        elif index == 2:
            feature_types = ['building-points']
        else:
            feature_types = ['roads']

        try:
            self.save_state()
            self.require_directory()
            for feature_type in feature_types:

                output_directory = self.output_directory.text()
                output_prefix = self.filename_prefix.text()
                overwrite = self.overwrite_checkBox.isChecked()
                output_base_file_path = self.get_output_base_path(
                    output_directory, output_prefix, feature_type, overwrite)

                self.download(feature_type, output_base_file_path)
                try:
                    self.load_shapefile(feature_type, output_base_file_path)
                except FileMissingError as exception:
                    display_warning_message_box(
                        self,
                        error_dialog_title,
                        exception.message)
            self.done(QDialog.Accepted)
            self.rectangle_map_tool.reset()

        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as exception:  # pylint: disable=broad-except
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            display_warning_message_box(
                self, error_dialog_title, exception.message)

            self.progress_dialog.cancel()

        finally:
            # Unlock the groupbox
            self.groupBox.setEnabled(True)

    def get_output_base_path(
            self,
            output_directory,
            output_prefix,
            feature_type,
            overwrite):
        """Get a full base name path to save the shapefile.

        :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 feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :param overwrite: Boolean to know if we can overwrite existing files.
        :type overwrite: bool

        :return: The base path.
        :rtype: str
        """
        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)

        :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 OsmDownloaderDialog.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.

        :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 download(self, feature_type, output_base_path):
        """Download shapefiles from Kartoza server.

        :param feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :param output_base_path: The base path of the shape file.
        :type output_base_path: str

        :raises: ImportDialogError, CanceledImportDialogError
        """

        # preparing necessary data
        min_longitude = str(self.min_longitude.text())
        min_latitude = str(self.min_latitude.text())
        max_longitude = str(self.max_longitude.text())
        max_latitude = str(self.max_latitude.text())

        box = (
            '{min_longitude},{min_latitude},{max_longitude},'
            '{max_latitude}').format(
                min_longitude=min_longitude,
                min_latitude=min_latitude,
                max_longitude=max_longitude,
                max_latitude=max_latitude
            )
        if feature_type == 'buildings':
            url = '{url}?bbox={box}&qgis_version=2'.format(
                url=self.buildings_url, box=box)
        elif feature_type == 'building-points':
            url = '{url}?bbox={box}&qgis_version=2'.format(
                url=self.building_points_url, box=box)
        else:
            url = '{url}?bbox={box}&qgis_version=2'.format(
                url=self.roads_url, box=box)

        if 'LANG' in os.environ:
            env_lang = os.environ['LANG']
            url += '&lang=%s' % env_lang

        path = tempfile.mktemp('.shp.zip')

        # download and extract it
        self.fetch_zip(url, path, feature_type)
        self.extract_zip(path, output_base_path)

        self.progress_dialog.done(QDialog.Accepted)

    def fetch_zip(self, url, output_path, feature_type):
        """Download zip containing shp file and write to output_path.

        :param url: URL of the zip bundle.
        :type url: str

        :param output_path: Path of output file,
        :type output_path: str

        :param feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :raises: ImportDialogError - when network error occurred
        """
        LOGGER.debug('Downloading file from URL: %s' % url)
        LOGGER.debug('Downloading to: %s' % output_path)

        self.progress_dialog.show()

        # Infinite progress bar when the server is fetching data.
        # The progress bar will be updated with the file size later.
        self.progress_dialog.setMaximum(0)
        self.progress_dialog.setMinimum(0)
        self.progress_dialog.setValue(0)

        # Get a pretty label from feature_type, but not translatable
        label_feature_type = feature_type.replace('-', ' ')

        label_text = self.tr('Fetching %s' % label_feature_type)
        self.progress_dialog.setLabelText(label_text)

        # Download Process
        downloader = FileDownloader(
            self.network_manager, url, output_path, self.progress_dialog)
        try:
            result = downloader.download()
        except IOError as ex:
            raise IOError(ex)

        if result[0] is not True:
            _, error_message = result

            if result[0] == QNetworkReply.OperationCanceledError:
                raise CanceledImportDialogError(error_message)
            else:
                raise DownloadError(error_message)

    @staticmethod
    def extract_zip(zip_path, destination_base_path):
        """Extract different extensions to the destination base path.

        Example : test.zip contains a.shp, a.dbf, a.prj
        and destination_base_path = '/tmp/CT-buildings
        Expected result :
            - /tmp/CT-buildings.shp
            - /tmp/CT-buildings.dbf
            - /tmp/CT-buildings.prj

        If two files in the zip with the same extension, only one will be
        copied.

        :param zip_path: The path of the .zip file
        :type zip_path: str

        :param destination_base_path: The destination base path where the shp
            will be written to.
        :type destination_base_path: str

        :raises: IOError - when not able to open path or output_dir does not
            exist.
        """

        import zipfile

        handle = open(zip_path, 'rb')
        zip_file = zipfile.ZipFile(handle)
        for name in zip_file.namelist():
            extension = os.path.splitext(name)[1]
            output_final_path = u'%s%s' % (destination_base_path, extension)
            output_file = open(output_final_path, 'wb')
            output_file.write(zip_file.read(name))
            output_file.close()

        handle.close()

    def load_shapefile(self, feature_type, base_path):
        """Load downloaded shape file to QGIS Main Window.

        :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')

        canvas_srid = self.canvas.mapRenderer().destinationCrs().srsid()
        on_the_fly_projection = self.canvas.hasCrsTransformEnabled()
        if canvas_srid != 4326 and not on_the_fly_projection:
            if QGis.QGIS_VERSION_INT >= 20400:
                self.canvas.setCrsTransformEnabled(True)
            else:
                display_warning_message_bar(
                    self.tr('Enable \'on the fly\''),
                    self.tr(
                        'Your current projection is different than EPSG:4326. '
                        'You should enable \'on the fly\' to display '
                        'correctly your layers')
                    )

    def reject(self):
        """Redefinition of the reject() method
        to remove the rectangle selection tool.
        It will call the super method.
        """

        self.canvas.unsetMapTool(self.rectangle_map_tool)
        self.rectangle_map_tool.reset()

        super(OsmDownloaderDialog, self).reject()
Пример #19
0
    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param parent: Parent widget of this dialog.
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: CRS for user defined analysis extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        icon = resources_path('img', 'icons', 'set-extents-tool.svg')
        self.setWindowIcon(QIcon(icon))

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:

            if isinstance(extent, QgsGeometry):
                # In InaSAFE V4, the extent is a QgsGeometry.
                # This like a hack to transform a geometry to a rectangle.
                extent = wkt_to_rectangle(extent.exportToWkt())

            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapSettings().destinationCrs())
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)
        # Set up context help
        self.help_button = self.button_box.button(QtGui.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)
        # Reset / Clear button
        clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

        # Populate the bookmarks list and connect the combobox
        self._populate_bookmarks_list()
        self.bookmarks_list.currentIndexChanged.connect(
            self.bookmarks_index_changed)

        # Reinstate the last used radio button
        mode = setting('analysis_extents_mode', HAZARD_EXPOSURE_VIEW)

        if mode == HAZARD_EXPOSURE_VIEW:
            self.hazard_exposure_view_extent.setChecked(True)
        elif mode == EXPOSURE:
            self.exposure_only.setChecked(True)
        elif mode == HAZARD_EXPOSURE:
            self.hazard_exposure_only.setChecked(True)
        elif mode == HAZARD_EXPOSURE_BOOKMARK:
            self.hazard_exposure_bookmark.setChecked(True)
        elif mode == HAZARD_EXPOSURE_BOUNDINGBOX:
            self.hazard_exposure_user_extent.setChecked(True)

        self.show_warnings.setChecked(
            setting('show_extent_warnings', True, bool))
        self.show_confirmations.setChecked(
            setting('show_extent_confirmations', True, bool))
Пример #20
0
    def __init__(self, iface, parent=None, extent=None, crs=None):
        """Constructor for the dialog.

        :param iface: A Quantum GIS QgisAppInterface instance.
        :type iface: QgisAppInterface

        :param parent: Parent widget of this dialog.
        :type parent: QWidget

        :param extent: Extent of the user's preferred analysis area.
        :type extent: QgsRectangle

        :param crs: CRS for user defined analysis extent.
        :type crs: QgsCoordinateReferenceSystem
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        icon = resources_path('img', 'icons', 'set-extents-tool.svg')
        self.setWindowIcon(QIcon(icon))

        self.iface = iface
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.previous_map_tool = None
        # Prepare the map tool
        self.tool = RectangleMapTool(self.canvas)
        self.previous_map_tool = self.canvas.mapTool()

        if extent is None:
            # Use the current map canvas extents as a starting point
            self.tool.set_rectangle(self.canvas.extent())
        else:

            if isinstance(extent, QgsGeometry):
                # In InaSAFE V4, the extent is a QgsGeometry.
                # This like a hack to transform a geometry to a rectangle.
                extent = wkt_to_rectangle(extent.asWkt())

            # Ensure supplied extent is in current canvas crs
            transform = QgsCoordinateTransform(
                crs,
                self.canvas.mapSettings().destinationCrs(),
                QgsProject.instance()
            )
            transformed_extent = transform.transformBoundingBox(extent)
            self.tool.set_rectangle(transformed_extent)

        self._populate_coordinates()

        # Observe inputs for changes
        self.x_minimum.valueChanged.connect(self._coordinates_changed)
        self.y_minimum.valueChanged.connect(self._coordinates_changed)
        self.x_maximum.valueChanged.connect(self._coordinates_changed)
        self.y_maximum.valueChanged.connect(self._coordinates_changed)

        # Draw the rubberband
        self._coordinates_changed()

        # Wire up button events
        self.capture_button.clicked.connect(self.start_capture)
        # Handle cancel
        cancel_button = self.button_box.button(
            QtWidgets.QDialogButtonBox.Cancel)
        cancel_button.clicked.connect(self.reject)
        # Make sure to reshow this dialog when rectangle is captured
        self.tool.rectangle_created.connect(self.stop_capture)
        # Setup ok button
        self.ok_button = self.button_box.button(QtWidgets.QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)
        # Set up 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)
        # Reset / Clear button
        clear_button = self.button_box.button(QtWidgets.QDialogButtonBox.Reset)
        clear_button.setText(self.tr('Clear'))
        clear_button.clicked.connect(self.clear)

        # Populate the bookmarks list and connect the combobox
        self._populate_bookmarks_list()
        self.bookmarks_list.currentIndexChanged.connect(
            self.bookmarks_index_changed)

        # Reinstate the last used radio button
        mode = setting('analysis_extents_mode', HAZARD_EXPOSURE_VIEW)

        if mode == HAZARD_EXPOSURE_VIEW:
            self.hazard_exposure_view_extent.setChecked(True)
        elif mode == EXPOSURE:
            self.exposure_only.setChecked(True)
        elif mode == HAZARD_EXPOSURE:
            self.hazard_exposure_only.setChecked(True)
        elif mode == HAZARD_EXPOSURE_BOOKMARK:
            self.hazard_exposure_bookmark.setChecked(True)
        elif mode == HAZARD_EXPOSURE_BOUNDINGBOX:
            self.hazard_exposure_user_extent.setChecked(True)

        self.show_warnings.setChecked(
            setting('show_extent_warnings', True, bool))
        self.show_confirmations.setChecked(
            setting('show_extent_confirmations', True, bool))