def process(self):
     self.count = 0
     if not os.path.exists(self.outputDir):
         qm = QtGui.QMessageBox
         ret = qm.question(self,'Create Directory?', "Output directory does not exist. Create?", qm.Yes | qm.No)
         
         if ret == qm.Yes:
             os.makedirs(self.outputDir)
         
     total2Proc = 100
    
     progress = QProgressDialog("Generating Adaptive Light Source Vectors. This can take approx. 5-15 mins.", "Abort", 0, total2Proc, self);
     progress.setWindowTitle("Generate AdaptiveLSV")
     progress.setWindowModality(QtCore.Qt.WindowModal);
     progress.show()
     QtGui.QApplication.processEvents()
     time.sleep(0.1)
     try:
         LSAdaptedi, LSAdapted, fname = self.genAdaptiveLSVectors(progress)
         progress.setValue(total2Proc);
         progress.cancel()
         qm = QtGui.QMessageBox
         qm.information(self,'Completed', 'Results can be found in {}\\{}.'.format(self.outputDir, fname))
     except Exception as e:
         progress.cancel()
         qm = QtGui.QMessageBox
         qm.information(self,'Error', '{}'.format(e))
         raise e
Example #2
0
    def process(self):
        startFolder = os.path.normpath(
            os.path.join(self.rootDir, str(self.startDir)))
        endFolder = os.path.normpath(
            os.path.join(self.rootDir, str(self.endDir)))

        d = glob.glob(os.path.join(self.rootDir, self.getLSType()))

        d.sort()
        d = [os.path.normpath(p) for p in d]

        if startFolder:
            startIdx = d.index(startFolder)
            endIdx = d.index(endFolder)

            d = d[startIdx:endIdx + 1]

        total2Proc = len(d)

        progress = QProgressDialog("Generating results...", "Abort", 0,
                                   total2Proc, self)
        progress.setWindowTitle("Batch Process")
        progress.setWindowModality(QtCore.Qt.WindowModal)

        try:
            if self.useAdaptivePS:

                pth2AdaptiveLSi = self.adaptiveLSVFile
                self.psNSourceAdaptiveBatch(
                    d,
                    pth2AdaptiveLSi,
                    self.getLSType(),
                    overrideLightingMode=self.getOverrideLSType(),
                    progressBar=progress)
                progress.setValue(total2Proc)

                qm = QtGui.QMessageBox
                qm.information(
                    self, 'Completed',
                    'SNZShadowImAndAlbedo_adaptiveLS.npz files created.')

            else:
                self.psNSourceBatch(d, progressBar=progress)
                progress.setValue(total2Proc)

                qm = QtGui.QMessageBox
                qm.information(self, 'Completed',
                               'SNZShadowImAndAlbedo.npz files created.')
        except Exception as e:
            progress.cancel()
            qm = QtGui.QMessageBox
            qm.information(self, 'Error', '{}'.format(e))
            raise e
    def process(self):
        dirs = sorted(glob.glob(self.rootDir + '\\*_mask*'))
        total2Proc = 100

        progress = QProgressDialog("Tracking leaves...", "Abort", 0,
                                   total2Proc, self)
        progress.setWindowTitle("Batch Process")
        progress.setWindowModality(QtCore.Qt.WindowModal)

        try:
            self.particleTracker(dirs, progressBar=progress)
            progress.setValue(total2Proc)

            qm = QtGui.QMessageBox
            qm.information(self, 'Completed', 'Leaf labels were changed.')

        except Exception as e:
            progress.cancel()
            qm = QtGui.QMessageBox
            qm.information(self, 'Error', '{}'.format(e))
            raise e
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()
Example #5
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 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 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.tr(country) for country in self.countries.keys()
        ]
        list_countries.sort()
        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()

    @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.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."""
        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:
            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)

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

        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)

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

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

        # Check if it's a building layer and if it's QGIS 2.14 about the 2.5D
        if qgis_version() >= 21400 and 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)

        canvas_srid = self.canvas.mapSettings().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 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()
    def process(self):
        now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
        if not os.path.exists(self.outputDir):
            qm = QtGui.QMessageBox
            ret = qm.question(
                self, 'Create Directory?',
                "Output results directory does not exist. Create?",
                qm.Yes | qm.No)

            if ret == qm.Yes:
                os.makedirs(self.outputDir)

        plantNos = [int(i) for i in self.edtPlants.text().split(',')]
        if not plantNos:
            qm.information(
                self, 'Error',
                'Please enter the plants to process separated by commas')
        startFolder = os.path.normpath(
            os.path.join(self.rootDir, str(self.startDir)))
        endFolder = os.path.normpath(
            os.path.join(self.rootDir, str(self.endDir)))

        d = glob.glob(os.path.join(self.rootDir, self.getLSType()))

        d.sort()
        d = [os.path.normpath(p) for p in d]

        if startFolder:
            startIdx = d.index(startFolder)
            endIdx = d.index(endFolder)

            d = d[startIdx:endIdx + 1]

        total2Proc = len(d) * len(plantNos)

        progress = QProgressDialog("Generating results...", "Abort", 0,
                                   total2Proc, self)
        progress.setWindowTitle("Results Generator")
        progress.setWindowModality(QtCore.Qt.WindowModal)

        if self.cbOverride.isChecked():
            sensorSize = [float(s) for s in self.sensorSize.split('x')]
            lens = ps.Lens(int(self.focalLength), sensorSize)
            camHeight = float(self.camHeight) * 10
        else:
            psconfig = PSConfig.fromPropertiesFile(
                os.path.join(d[0], 'psconfig.properties'))
            lens = ps.PGLens(int(psconfig.lens))
            camHeight = psconfig.LS_VIS[0][2] * 10

        imsz = cv2.imread(os.path.join(d[0], 'im0.bmp'),
                          cv2.IMREAD_GRAYSCALE).shape

        h, v = LensSensorPixelCalc(lens.focalLen, lens.sensorSize, camHeight,
                                   imsz)

        flag = 0

        try:
            labelsDicts = {}
            plantRegionProps = dict()
            for i, p in enumerate(plantNos):
                labels = dict()
                if progress.wasCanceled():
                    break
                raAll = []
                leafStatsAll = []
                for n, pth in enumerate(d):
                    progress.setValue(i * len(d) + n)
                    if progress.wasCanceled():
                        break

                    print('Processing Plant: {}, {}'.format(p, pth))

                    A, z, snIm, shadowIm = loadPlant(pth, p, self.roiFile)
                    session = os.path.basename(pth)
                    maskFname = os.path.join(
                        self.maskDir, '{}_{}_mask.png'.format(p, session))
                    mask = cv2.imread(maskFname)

                    if mask is None:
                        raise Exception(
                            'Mask file {} not found.'.format(maskFname))

                    if len(np.unique(mask)) == 2:
                        mask = cv2.cvtColor(mask, cv2.COLOR_RGB2GRAY)
                        mask = mask.astype(bool)
                        flag = 1

                    else:
                        labelMask = cv2.imread(maskFname)
                        im = Image.fromarray(labelMask)
                        labelMask = im.convert(mode="P",
                                               palette=Image.ADAPTIVE)

                        if p not in labelsDicts.keys():
                            labelsDicts[p] = {}

                        labelMask, labelsDicts[p] = self.convert2IndexedLabels(
                            labelMask, labelsDicts[p])

                        labels.update({p: labelMask})
                        self.dumpMaskLabelImage(session, p, labelMask)

                        leaves, leafMasks, bladeMasks, stemMasks, processedLeafLabels = self.getLeafNormals(
                            snIm, labels[p])
                        plantLeavesStats = self.getStats(
                            leaves, leafMasks, bladeMasks, stemMasks,
                            processedLeafLabels, h, v)
                        leafStatsAll.append(
                            [os.path.basename(pth), plantLeavesStats])

                        mask = cv2.cvtColor(mask, cv2.COLOR_RGB2GRAY)
                        mask = mask.astype(bool)

                    Nx = snIm[:, :, 0]
                    Ny = snIm[:, :, 1]
                    Nz = snIm[:, :, 2]
                    area2D, areaNz, h, v = self.estimateRosetteArea(
                        mask, Nz, camHeight, lens.focalLen)
                    print('2D area: {:.2f}, 3D area: {:.2f}'.format(
                        area2D, areaNz))

                    if flag:
                        tmp = copy.deepcopy(mask)
                        tmp[mask == True] = 1
                        tmp[mask == False] = 0
                        tmp = tmp.astype(int)
                        props = regionprops(tmp)[0]

                    else:
                        label_img = label(mask)
                        props = regionprops(label_img)[0]
                    p_rw = props.perimeter * h
                    circ = (4 * math.pi * props.area) / (props.perimeter *
                                                         props.perimeter)
                    comp = props.solidity
                    coords = find_contours(mask, .5)[0]
                    D = pdist(coords)
                    D = squareform(D)
                    N, [I_row, I_col] = np.nanmax(D) * h, np.unravel_index(
                        np.argmax(D), D.shape)

                    az, elev, r = ps.cart2sph2(Nx, Ny, Nz)
                    elev = (90 - np.degrees(elev.copy()))
                    elev[mask == False] = np.nan
                    meanElev = np.nanmean(elev)
                    raAll.append([
                        area2D, areaNz, p_rw, circ, comp, N, meanElev,
                        os.path.basename(pth)
                    ])

                self.dumpRA2CSV(
                    raAll,
                    os.path.join(
                        self.outputDir, '{}_{}_{}_{}_{}_rosette'.format(
                            now, p, os.path.basename(self.rootDir),
                            os.path.basename(startFolder),
                            os.path.basename(endFolder))))
                if len(leafStatsAll):
                    self.dumpLeaves2CSV(
                        leafStatsAll,
                        os.path.join(
                            self.outputDir, '{}_{}_{}_{}_{}_leaves'.format(
                                now, p, os.path.basename(self.rootDir),
                                os.path.basename(startFolder),
                                os.path.basename(endFolder))))

            progress.setValue(total2Proc)
            qm = QtGui.QMessageBox
            qm.information(
                self, 'Completed',
                'Results can be found in {}.'.format(self.outputDir))
        except Exception as e:
            progress.cancel()
            qm = QtGui.QMessageBox
            qm.information(self, 'Error', '{}'.format(e))
            raise e
Example #7
0
    def onPlotButton(self):
        """ Batch plots selected geometry items using the selected template and scale.
        """
        # check if one layer is selected
        if self.ui.LayersComboBox.currentIndex() == -1:
            QMessageBox.warning(self, tr("Warning"), tr("Select a layer!"))
            self.ui.LayersComboBox.setFocus()
            return
        # check if one composition template is selected
        if self.ui.TemplateList.selectedItems() == []:
            QMessageBox.warning(self, tr("Warning"), tr("Select a composer template!"))
            self.ui.TemplateList.setFocus()
            return
        template_filename = QDir(self.templatepath).absoluteFilePath(
                                      self.ui.TemplateList.currentItem().text())
        
        # get the scale
        if self.ui.ScaleCombo.currentText()=="<extent>":
            scale = -1
        else:
            try:
                scale = int(self.ui.ScaleCombo.currentText())
            except (ValueError):
                QMessageBox.warning(self, tr("Warning"), tr("Scale must be a positive integer value!"))
                self.ui.ScaleCombo.setFocus()
                return
            if scale<=0:
                QMessageBox.warning(self, tr("Warning"), tr("Scale must be a positive integer value!"))
                self.ui.ScaleCombo.setFocus()
                return
        
        # get composer name
        composer_name = self.ui.ComposerEdit.text()
        
        #check if there are selected items on polygon layers
        if self.batch_plotting:
            selected_layer = self.ui.LayersComboBox.itemData(self.ui.LayersComboBox.currentIndex())
            selected_polygons = get_features(selected_layer.name(),QGis.Polygon,True)
            if selected_polygons is None:
                QMessageBox.warning(self, tr("Warning"),
                    tr("Select at least one polygon on layer '%s'!"%selected_layer.name()))
                return
        
        # check output setting
        if self.ui.OutputTab.currentIndex() == 0:    # to PDF
            if not self.ui.SingleFileCheckbox.checkState():
                if len( self.ui.OutputPDFEdit.text() ) == 0:
                    res = QMessageBox.warning(self, tr("Warning"),
                        tr("The filename pattern is empty. A default one will be used."),
                        QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok)
                    if res == QMessageBox.Cancel:
                        return
                    self.ui.OutputPDFEdit.setText( QgsAtlasComposition(None).filenamePattern() )
        elif self.ui.OutputTab.currentIndex() == 1:  # to Printer
            # no need for checking
            pass
        elif self.ui.OutputTab.currentIndex() == 2:  # to Composer View
            # no need for checking yet
            pass

        # get map renderer of map canvas        
        renderer = self.iface.mapCanvas().mapRenderer()
        self.composition = QgsComposition(renderer)

        # if plot to Composer View the composition must be set 
        # before loading the template 
        # otherwise composer's item properties doesn't appear
        if self.ui.OutputTab.currentIndex() == 2:  # to Composer View
            if len(composer_name)==0:
                composer = self.iface.createNewComposer()
            else: 
                composer = self.iface.createNewComposer(composer_name)
            composer.setComposition(self.composition)

        # read template file and add to composition
        template_file = QFile( template_filename )
        template_file.open(QIODevice.ReadOnly | QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()
        document = QDomDocument()
        document.setContent(template_content)
        self.composition.loadFromTemplate(document)

        # if batch_plotting is True create an atlas composition
        if self.batch_plotting:
            # get composer map item and set new scale and the grid
            cmap = self.composition.getComposerMapById(0)
            cmap.setNewScale(scale)
            cmap.setGridIntervalX(scale/10)
            cmap.setGridIntervalY(scale/10)
            cmap.setAtlasDriven(True)
            cmap.setAtlasScalingMode( QgsComposerMap.Fixed )

            # set atlas composition parameters
            atlas = self.composition.atlasComposition()
            atlas.setEnabled(True)
            atlas.setCoverageLayer( selected_layer )
            atlas.setHideCoverage(False)
            atlas.setFilenamePattern( self.ui.OutputPDFEdit.text() )
            atlas.setSingleFile( self.ui.SingleFileCheckbox.checkState() )
            atlas.setSortFeatures(False)
            atlas.setFilterFeatures(True)
            selected_ids = [f.id() for f in selected_layer.selectedFeatures()]
            filter_id_string = ','.join([str(sid) for sid in selected_ids])
            atlas.setFeatureFilter("$id in (" + filter_id_string + ")")

            # print the complete atlas composition
            if self.ui.OutputTab.currentIndex() == 0:    # to PDF
                self.composition.setAtlasMode( QgsComposition.ExportAtlas )
                
                if self.pdfpath=="":
                    self.pdfpath = QgsProject.instance().homePath().encode(sys.getfilesystemencoding())
                    
                if self.ui.SingleFileCheckbox.checkState():
                    #print to single pdf (multi-page)
                    outputFileName = QDir(self.pdfpath).absoluteFilePath("qgis.pdf")
                    outputFileName = QFileDialog.getSaveFileName(self,
                       tr( "Choose a file name to save the map as" ),
                       outputFileName,
                       tr( "PDF Format" ) + " (*.pdf *.PDF)" )
                    if not outputFileName:
                        return
                    if not outputFileName.lower().endswith(".pdf"):
                        outputFileName += ".pdf"
                    self.pdfpath = QDir(outputFileName).absolutePath()
                else:
                    #print to more pdf
                    outputDir = QFileDialog.getExistingDirectory( self,
                        tr( "Directory where to save PDF files" ),
                        self.pdfpath,
                        QFileDialog.ShowDirsOnly )
                    if not outputDir:
                        return
                    # test directory (if it exists and is writable)
                    if not QDir(outputDir).exists() or not QFileInfo(outputDir).isWritable():
                        QMessageBox.warning( self, tr( "Unable to write into the directory" ),
                            tr( "The given output directory is not writable. Cancelling." ) )
                        return
                    self.pdfpath = outputDir
                
                printer = QPrinter()
                painter = QPainter()
                if not len(atlas.featureFilterErrorString()) == 0:
                    QMessageBox.warning( self, tr( "Atlas processing error" ),
                        tr( "Feature filter parser error: %s" % atlas.featureFilterErrorString() ) )
                    return

                atlas.beginRender()

                if self.ui.SingleFileCheckbox.checkState():
                    #prepare for first feature, so that we know paper size to begin with
                    atlas.prepareForFeature(0)
                    self.composition.beginPrintAsPDF(printer, outputFileName)
                    # set the correct resolution
                    self.composition.beginPrint(printer)
                    printReady =  painter.begin(printer)
                    if not printReady:
                        QMessageBox.warning( self, tr( "Atlas processing error" ),
                              tr( "Error creating %s." % outputFileName ) )
                        return
                    
                progress = QProgressDialog( tr( "Rendering maps..." ), tr( "Abort" ), 0, atlas.numFeatures(), self )
                QApplication.setOverrideCursor( Qt.BusyCursor )
                
                for featureI in range(0, atlas.numFeatures()):
                    progress.setValue( featureI+1 )
                    # process input events in order to allow aborting
                    QCoreApplication.processEvents()
                    if progress.wasCanceled():
                        atlas.endRender()
                        break
                    if not atlas.prepareForFeature( featureI ):
                        QMessageBox.warning( self, tr( "Atlas processing error" ),
                              tr( "Atlas processing error" ) )
                        progress.cancel()
                        QApplication.restoreOverrideCursor()
                        return
                    if not self.ui.SingleFileCheckbox.checkState():
                        multiFilePrinter = QPrinter()
                        outputFileName = QDir( outputDir ).filePath( atlas.currentFilename() ) + ".pdf"
                        self.composition.beginPrintAsPDF( multiFilePrinter, outputFileName )
                        # set the correct resolution
                        self.composition.beginPrint( multiFilePrinter )
                        printReady = painter.begin( multiFilePrinter )
                        if not printReady:
                            QMessageBox.warning( self, tr( "Atlas processing error" ),
                                tr( "Error creating %s." % outputFileName ) )
                            progress.cancel()
                            QApplication.restoreOverrideCursor()
                            return
                        self.composition.doPrint( multiFilePrinter, painter )
                        painter.end()
                    else:
                        # start print on a new page if we're not on the first feature
                        if featureI > 0:
                            printer.newPage()
                        self.composition.doPrint( printer, painter )
                    
                atlas.endRender()
                if self.ui.SingleFileCheckbox.checkState():
                    painter.end()
                QApplication.restoreOverrideCursor()

            elif self.ui.OutputTab.currentIndex() == 1:  # to Printer
                # if To Printer is selected set the printer
                # setting up printer
                if self.printer is None:
                    self.printer = QPrinter()
                    self.printer.setFullPage(True)
                    self.printer.setColorMode(QPrinter.Color)
                # open printer setting dialog
                pdlg = QPrintDialog(self.printer,self)
                pdlg.setModal(True)
                pdlg.setOptions(QAbstractPrintDialog.None)
                if not pdlg.exec_() == QDialog.Accepted:
                    return
                
                QApplication.setOverrideCursor(Qt.BusyCursor)
                #prepare for first feature, so that we know paper size to begin with
                self.composition.setAtlasMode( QgsComposition.ExportAtlas )
                atlas.prepareForFeature(0)

                # set orientation                
                if self.composition.paperWidth() > self.composition.paperHeight():
                    self.printer.setOrientation(QPrinter.Landscape)
                    self.printer.setPaperSize(
                        QSizeF(self.composition.paperHeight(), self.composition.paperWidth()),
                        QPrinter.Millimeter)
                else:
                    self.printer.setOrientation(QPrinter.Portrait)
                    self.printer.setPaperSize(
                        QSizeF(self.composition.paperWidth(), self.composition.paperHeight()),
                        QPrinter.Millimeter)
                self.printer.setResolution(self.composition.printResolution())

                self.composition.beginPrint( self.printer )
                painter = QPainter(self.printer)
                if not len(atlas.featureFilterErrorString()) == 0:
                    QMessageBox.warning( self, tr( "Atlas processing error" ),
                        tr( "Feature filter parser error: %s" % atlas.featureFilterErrorString() ) )
                    QApplication.restoreOverrideCursor()
                    return

                atlas.beginRender()
                progress = QProgressDialog( tr( "Rendering maps..." ), tr( "Abort" ), 0, atlas.numFeatures(), self )
                for featureI in range(0, atlas.numFeatures()):
                    progress.setValue( featureI+1 )
                    # process input events in order to allow cancelling
                    QCoreApplication.processEvents()
                    if progress.wasCanceled():
                        atlas.endRender()
                        break
                    if not atlas.prepareForFeature( featureI ):
                        QMessageBox.warning( self, tr( "Atlas processing error" ),
                              tr( "Atlas processing error" ) )
                        progress.cancel()
                        QApplication.restoreOverrideCursor()
                        return

                    # start print on a new page if we're not on the first feature
                    if featureI > 0:
                        self.printer.newPage()
                    self.composition.doPrint( self.printer, painter )
                
                atlas.endRender()
                painter.end()
                QApplication.restoreOverrideCursor()

            elif self.ui.OutputTab.currentIndex() == 2:  # to Composer View
                # create new composer
                self.composition.setAtlasMode( QgsComposition.PreviewAtlas )
                composer.composerWindow().on_mActionAtlasPreview_triggered(True)
                atlas.parameterChanged.emit()
                # Increase the reference count of the composer object 
                # for not being garbage collected.
                # If not doing this composer would lost reference and qgis would crash 
                # when referring to this composer object or at quit.
                ctypes.c_long.from_address( id(composer) ).value += 1
        else:
            # if batch_plotting is False open a QgsComposerView with current map canvas
            cmap = self.composition.getComposerMapById(0)
            # set the new extent of composer map item
            newextent = self.iface.mapCanvas().mapRenderer().extent()
            currentextent = cmap.extent()
            canvas_ratio = newextent.width()/newextent.height()
            map_ratio = currentextent.width()/currentextent.height()
            if map_ratio < canvas_ratio:
                dh = newextent.width() / map_ratio - newextent.height()
                newextent.setYMinimum( newextent.yMinimum() - dh / 2 )
                newextent.setYMaximum( newextent.yMaximum() + dh / 2 )
            else:
                dw = map_ratio * newextent.height() - newextent.width()
                newextent.setXMinimum( newextent.xMinimum() - dw / 2 )
                newextent.setXMaximum( newextent.xMaximum() + dw / 2 )
            cmap.setNewExtent(newextent)
            # set the new scale of composer map item
            if scale>0:
                cmap.setNewScale(scale)
            sc = cmap.scale()
            # set the grid interval according to the scale
            cmap.setGridIntervalX(sc/10)
            cmap.setGridIntervalY(sc/10)
            # Increase the reference count of the composer object 
            # for not being garbage collected.
            # If not doing this composer would lost reference and qgis would crash 
            # when referring to this composer object or at quit.
            ctypes.c_long.from_address( id(composer) ).value += 1

        self.accept()
Example #8
0
class OsmDownloader(QDialog, Ui_OsmDownloaderBase):
    """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.url = "http://osm.linfiniti.com/buildings-shp"

        # creating progress dialog for download
        self.progressDialog = QProgressDialog(self)
        self.progressDialog.setAutoClose(False)
        myTitle = self.tr("InaSAFE OpenStreetMap Downloader")
        self.progressDialog.setWindowTitle(myTitle)
        # Set up context help
        helpButton = self.buttonBox.button(QtGui.QDialogButtonBox.Help)
        QtCore.QObject.connect(helpButton, QtCore.SIGNAL('clicked()'),
                               self.show_help)

        self.show_info()

        self.network_manager = QNetworkAccessManager(self)
        self.restore_state()
        self.update_extent()

    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\') 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(
            'Use QGIS to zoom in to the area for which you want building data '
            'to be retrieved.'))
        tips.add(self.tr(
            'Check the output directory is correct. Note that the saved '
            'dataset will be called buildings.shp (and its associated files).'
        ))
        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 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.webView.setHtml(string)

    def restore_state(self):
        """ Read last state of GUI from configuration file."""
        mySetting = QSettings()
        self.outDir.setText(mySetting.value('directory'))

    def save_state(self):
        """ Store current state of GUI to configuration file """
        mySetting = QSettings()
        mySetting.setValue('directory', self.outDir.text())

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

    def update_extent(self):
        """ Update extent value in GUI based from value in map."""
        myExtent = self.iface.mapCanvas().extent()
        self.minLongitude.setText(str(myExtent.xMinimum()))
        self.minLatitude.setText(str(myExtent.yMinimum()))
        self.maxLongitude.setText(str(myExtent.xMaximum()))
        self.maxLatitude.setText(str(myExtent.yMaximum()))

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

    def accept(self):
        """Do osm download and display it in QGIS."""

        try:
            self.save_state()

            self.require_directory()
            self.download()
            self.load_shapefile()
            self.done(QDialog.Accepted)
        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as myEx:
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QMessageBox.warning(
                self,
                self.tr("InaSAFE OpenStreetMap downloader error"),
                str(myEx))

            self.progressDialog.cancel()

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

        myDir = str(self.outDir.text())

        if os.path.exists(myDir):
            return

        myTitle = self.tr("Directory %s not exist") % myDir
        myQuestion = self.tr(
            "Directory %s not exist. Do you want to create it?"
        ) % myDir
        # noinspection PyCallByClass,PyTypeChecker
        myAnswer = QMessageBox.question(
            self, myTitle,
            myQuestion, QMessageBox.Yes | QMessageBox.No)

        if myAnswer == QMessageBox.Yes:
            os.makedirs(myDir)
        else:
            raise CanceledImportDialogError()

    def download(self):
        """Download shapefiles from Linfinti server.

        :raises: ImportDialogError, CanceledImportDialogError
        """

        ## preparing necessary data
        myMinLng = str(self.minLongitude.text())
        myMinLat = str(self.minLatitude.text())
        myMaxLng = str(self.maxLongitude.text())
        myMaxLat = str(self.maxLatitude.text())

        myCoordinate = "{myMinLng},{myMinLat},{myMaxLng},{myMaxLat}".format(
            myMinLng=myMinLng,
            myMinLat=myMinLat,
            myMaxLng=myMaxLng,
            myMaxLat=myMaxLat
        )

        myShapeUrl = "{url}?bbox={myCoordinate}".format(
            url=self.url,
            myCoordinate=myCoordinate
        )

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

        # download and extract it
        self.fetch_zip(myShapeUrl, myFilePath)
        print myFilePath
        print str(self.outDir.text())
        self.extract_zip(myFilePath, str(self.outDir.text()))

        self.progressDialog.done(QDialog.Accepted)

    def fetch_zip(self, url, output_path):
        """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

        :raises: ImportDialogError - when network error occurred
        """

        self.progressDialog.show()
        self.progressDialog.setMaximum(100)
        self.progressDialog.setValue(0)

        # myLabelText = "Begin downloading shapefile from " \
        #               + "%s ..."
        # self.progressDialog.setLabelText(self.tr(myLabelText) % (url))
        myLabelText = self.tr("Downloading shapefile")
        self.progressDialog.setLabelText(myLabelText)

        myResult = download_url(
            self.network_manager, url, output_path,
            self.progressDialog)

        if myResult is not True:
            _, myErrorMessage = myResult
            raise ImportDialogError(myErrorMessage)

    def extract_zip(self, path, output_dir):
        """Extract all content of a .zip file from path to output_dir.

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

        :param output_dir: Output directory where the shp will be written to.
        :type output_dir: str

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

        import zipfile

        # extract all files...
        myHandle = open(path, 'rb')
        myZip = zipfile.ZipFile(myHandle)
        for myName in myZip.namelist():
            myOutPath = os.path.join(output_dir, myName)
            myOutFile = open(myOutPath, 'wb')
            myOutFile.write(myZip.read(myName))
            myOutFile.close()

        myHandle.close()

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

        :raises: ImportDialogError - when buildings.shp not exist
        """

        myDir = str(self.outDir.text())
        myPath = os.path.join(myDir, 'buildings.shp')

        if not os.path.exists(myPath):
            myMessage = self.tr(
                "%s don't exist. The server don't have buildings data."
            )
            raise ImportDialogError(myMessage)

        self.iface.addVectorLayer(myPath, 'buildings', 'ogr')
Example #9
0
class OsmDownloader(QDialog, Ui_OsmDownloaderBase):
    """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.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.setValidator(validator)

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

    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 will be used to determine the area for which '
            'you want data to be retrieved. You can adjust it manually using '
            'the bounding box options below.'))
        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):
        """ Update extent value in GUI based from value in map."""
        # Get the extent as [xmin, ymin, xmax, ymax]
        extent = viewport_geo_array(self.iface.mapCanvas())
        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]))

    @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 accept(self):
        """Do osm download and display it in QGIS."""

        index = self.feature_type.currentIndex()
        if index == 0:
            feature_types = ['buildings', 'roads']
        elif index == 1:
            feature_types = ['buildings']
        else:
            feature_types = ['roads']

        try:
            self.save_state()
            self.require_directory()
            for feature_type in feature_types:
                self.download(feature_type)
                self.load_shapefile(feature_type)
            self.done(QDialog.Accepted)
        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as myEx:
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QMessageBox.warning(
                self,
                self.tr("InaSAFE OpenStreetMap downloader error"),
                str(myEx))

            self.progress_dialog.cancel()

    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 = str(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
                QMessageBox.warning(
                    self,
                    self.tr('InaSAFE error'),
                    self.tr('Output directory can not be empty.'))
                raise CanceledImportDialogError()
        else:
            raise CanceledImportDialogError()

    def download(self, feature_type):
        """Download shapefiles from Linfinti server.

        :param feature_type: What kind of features should be downloaded.
            Currently 'buildings' or 'roads' are supported.
        :type feature_type: 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
            )
        output_prefix = self.filename_prefix.text()
        if feature_type == 'buildings':
            url = "{url}?bbox={box}&qgis_version=2".format(
                url=self.buildings_url, box=box)
        else:
            url = "{url}?bbox={box}&qgis_version=2".format(
                url=self.roads_url, box=box)

        if output_prefix is not None:
            url += '&output_prefix=%s' % output_prefix

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

        # download and extract it
        self.fetch_zip(url, path)
        #print path
        #print str(self.output_directory.text())

        self.extract_zip(path, str(self.output_directory.text()))

        self.progress_dialog.done(QDialog.Accepted)

    def fetch_zip(self, url, output_path):
        """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

        :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()
        self.progress_dialog.setMaximum(100)
        self.progress_dialog.setValue(0)

        # label_text = "Begin downloading shapefile from " \
        #               + "%s ..."
        # self.progress_dialog.setLabelText(self.tr(label_text) % (url))
        label_text = self.tr("Downloading shapefile")
        self.progress_dialog.setLabelText(label_text)

        result = download_url(
            self.network_manager, url, output_path,
            self.progress_dialog)

        if result[0] is not True:
            _, error_message = result
            raise ImportDialogError(error_message)

    @staticmethod
    def extract_zip(path, output_dir):
        """Extract all content of a .zip file from path to output_dir.

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

        :param output_dir: Output directory where the shp will be written to.
        :type output_dir: str

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

        import zipfile

        # extract all files...
        handle = open(path, 'rb')
        zip_file = zipfile.ZipFile(handle)
        for name in zip_file.namelist():
            output_path = os.path.join(output_dir, name)
            output_file = open(output_path, 'wb')
            output_file.write(zip_file.read(name))
            output_file.close()

        handle.close()

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

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

        :raises: ImportDialogError - when buildings.shp not exist
        """
        output_prefix = self.filename_prefix.text()
        path = str(self.output_directory.text())
        path = os.path.join(path, '%s%s.shp' % (output_prefix, feature_type))

        if not os.path.exists(path):
            message = self.tr(
                "%s don't exist. The server doesn't have any data."
            )
            raise ImportDialogError(message)

        self.iface.addVectorLayer(path, feature_type, 'ogr')
Example #10
0
    def onPlotButton(self):
        """ Batch plots selected geometry items using the selected template and scale.
        """
        # check if one layer is selected
        if self.ui.LayersComboBox.currentIndex() == -1:
            QMessageBox.warning(self, tr("Warning"), tr("Select a layer!"))
            self.ui.LayersComboBox.setFocus()
            return
        # check if one composition template is selected
        if self.ui.TemplateList.selectedItems() == []:
            QMessageBox.warning(self, tr("Warning"),
                                tr("Select a composer template!"))
            self.ui.TemplateList.setFocus()
            return
        template_filename = QDir(self.templatepath).absoluteFilePath(
            self.ui.TemplateList.currentItem().text())

        # get the scale
        if self.ui.ScaleCombo.currentText() == "<extent>":
            scale = -1
        else:
            try:
                scale = int(self.ui.ScaleCombo.currentText())
            except (ValueError):
                QMessageBox.warning(
                    self, tr("Warning"),
                    tr("Scale must be a positive integer value!"))
                self.ui.ScaleCombo.setFocus()
                return
            if scale <= 0:
                QMessageBox.warning(
                    self, tr("Warning"),
                    tr("Scale must be a positive integer value!"))
                self.ui.ScaleCombo.setFocus()
                return

        # get composer name
        composer_name = self.ui.ComposerEdit.text()

        #check if there are selected items on polygon layers
        if self.batch_plotting:
            selected_layer = self.ui.LayersComboBox.itemData(
                self.ui.LayersComboBox.currentIndex())
            selected_polygons = get_features(selected_layer.name(),
                                             QGis.Polygon, True)
            if selected_polygons is None:
                QMessageBox.warning(
                    self, tr("Warning"),
                    tr("Select at least one polygon on layer '%s'!" %
                       selected_layer.name()))
                return

        # check output setting
        if self.ui.OutputTab.currentIndex() == 0:  # to PDF
            if not self.ui.SingleFileCheckbox.checkState():
                if len(self.ui.OutputPDFEdit.text()) == 0:
                    res = QMessageBox.warning(
                        self, tr("Warning"),
                        tr("The filename pattern is empty. A default one will be used."
                           ), QMessageBox.Ok | QMessageBox.Cancel,
                        QMessageBox.Ok)
                    if res == QMessageBox.Cancel:
                        return
                    self.ui.OutputPDFEdit.setText(
                        QgsAtlasComposition(None).filenamePattern())
        elif self.ui.OutputTab.currentIndex() == 1:  # to Printer
            # no need for checking
            pass
        elif self.ui.OutputTab.currentIndex() == 2:  # to Composer View
            # no need for checking yet
            pass

        # get map renderer of map canvas
        renderer = self.iface.mapCanvas().mapRenderer()
        self.composition = QgsComposition(renderer)

        # if plot to Composer View the composition must be set
        # before loading the template
        # otherwise composer's item properties doesn't appear
        if self.ui.OutputTab.currentIndex() == 2:  # to Composer View
            if len(composer_name) == 0:
                composer = self.iface.createNewComposer()
            else:
                composer = self.iface.createNewComposer(composer_name)
            composer.setComposition(self.composition)

        # read template file and add to composition
        template_file = QFile(template_filename)
        template_file.open(QIODevice.ReadOnly | QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()
        document = QDomDocument()
        document.setContent(template_content)
        self.composition.loadFromTemplate(document)

        # if batch_plotting is True create an atlas composition
        if self.batch_plotting:
            # get composer map item and set new scale and the grid
            cmap = self.composition.getComposerMapById(0)
            cmap.setNewScale(scale)
            cmap.setGridIntervalX(scale / 10)
            cmap.setGridIntervalY(scale / 10)
            cmap.setAtlasDriven(True)
            cmap.setAtlasScalingMode(QgsComposerMap.Fixed)

            # set atlas composition parameters
            atlas = self.composition.atlasComposition()
            atlas.setEnabled(True)
            atlas.setCoverageLayer(selected_layer)
            atlas.setHideCoverage(False)
            atlas.setFilenamePattern(self.ui.OutputPDFEdit.text())
            atlas.setSingleFile(self.ui.SingleFileCheckbox.checkState())
            atlas.setSortFeatures(False)
            atlas.setFilterFeatures(True)
            selected_ids = [f.id() for f in selected_layer.selectedFeatures()]
            filter_id_string = ','.join([str(sid) for sid in selected_ids])
            atlas.setFeatureFilter("$id in (" + filter_id_string + ")")

            # print the complete atlas composition
            if self.ui.OutputTab.currentIndex() == 0:  # to PDF
                self.composition.setAtlasMode(QgsComposition.ExportAtlas)

                if self.pdfpath == "":
                    self.pdfpath = QgsProject.instance().homePath().encode(
                        sys.getfilesystemencoding())

                if self.ui.SingleFileCheckbox.checkState():
                    #print to single pdf (multi-page)
                    outputFileName = QDir(
                        self.pdfpath).absoluteFilePath("qgis.pdf")
                    outputFileName = QFileDialog.getSaveFileName(
                        self, tr("Choose a file name to save the map as"),
                        outputFileName,
                        tr("PDF Format") + " (*.pdf *.PDF)")
                    if not outputFileName:
                        return
                    if not outputFileName.lower().endswith(".pdf"):
                        outputFileName += ".pdf"
                    self.pdfpath = QDir(outputFileName).absolutePath()
                else:
                    #print to more pdf
                    outputDir = QFileDialog.getExistingDirectory(
                        self, tr("Directory where to save PDF files"),
                        self.pdfpath, QFileDialog.ShowDirsOnly)
                    if not outputDir:
                        return
                    # test directory (if it exists and is writable)
                    if not QDir(outputDir).exists() or not QFileInfo(
                            outputDir).isWritable():
                        QMessageBox.warning(
                            self, tr("Unable to write into the directory"),
                            tr("The given output directory is not writable. Cancelling."
                               ))
                        return
                    self.pdfpath = outputDir

                printer = QPrinter()
                painter = QPainter()
                if not len(atlas.featureFilterErrorString()) == 0:
                    QMessageBox.warning(
                        self, tr("Atlas processing error"),
                        tr("Feature filter parser error: %s" %
                           atlas.featureFilterErrorString()))
                    return

                atlas.beginRender()

                if self.ui.SingleFileCheckbox.checkState():
                    #prepare for first feature, so that we know paper size to begin with
                    atlas.prepareForFeature(0)
                    self.composition.beginPrintAsPDF(printer, outputFileName)
                    # set the correct resolution
                    self.composition.beginPrint(printer)
                    printReady = painter.begin(printer)
                    if not printReady:
                        QMessageBox.warning(
                            self, tr("Atlas processing error"),
                            tr("Error creating %s." % outputFileName))
                        return

                progress = QProgressDialog(tr("Rendering maps..."),
                                           tr("Abort"), 0, atlas.numFeatures(),
                                           self)
                QApplication.setOverrideCursor(Qt.BusyCursor)

                for featureI in range(0, atlas.numFeatures()):
                    progress.setValue(featureI + 1)
                    # process input events in order to allow aborting
                    QCoreApplication.processEvents()
                    if progress.wasCanceled():
                        atlas.endRender()
                        break
                    if not atlas.prepareForFeature(featureI):
                        QMessageBox.warning(self, tr("Atlas processing error"),
                                            tr("Atlas processing error"))
                        progress.cancel()
                        QApplication.restoreOverrideCursor()
                        return
                    if not self.ui.SingleFileCheckbox.checkState():
                        multiFilePrinter = QPrinter()
                        outputFileName = QDir(outputDir).filePath(
                            atlas.currentFilename()) + ".pdf"
                        self.composition.beginPrintAsPDF(
                            multiFilePrinter, outputFileName)
                        # set the correct resolution
                        self.composition.beginPrint(multiFilePrinter)
                        printReady = painter.begin(multiFilePrinter)
                        if not printReady:
                            QMessageBox.warning(
                                self, tr("Atlas processing error"),
                                tr("Error creating %s." % outputFileName))
                            progress.cancel()
                            QApplication.restoreOverrideCursor()
                            return
                        self.composition.doPrint(multiFilePrinter, painter)
                        painter.end()
                    else:
                        # start print on a new page if we're not on the first feature
                        if featureI > 0:
                            printer.newPage()
                        self.composition.doPrint(printer, painter)

                atlas.endRender()
                if self.ui.SingleFileCheckbox.checkState():
                    painter.end()
                QApplication.restoreOverrideCursor()

            elif self.ui.OutputTab.currentIndex() == 1:  # to Printer
                # if To Printer is selected set the printer
                # setting up printer
                if self.printer is None:
                    self.printer = QPrinter()
                    self.printer.setFullPage(True)
                    self.printer.setColorMode(QPrinter.Color)
                # open printer setting dialog
                pdlg = QPrintDialog(self.printer, self)
                pdlg.setModal(True)
                pdlg.setOptions(QAbstractPrintDialog.None)
                if not pdlg.exec_() == QDialog.Accepted:
                    return

                QApplication.setOverrideCursor(Qt.BusyCursor)
                #prepare for first feature, so that we know paper size to begin with
                self.composition.setAtlasMode(QgsComposition.ExportAtlas)
                atlas.prepareForFeature(0)

                # set orientation
                if self.composition.paperWidth(
                ) > self.composition.paperHeight():
                    self.printer.setOrientation(QPrinter.Landscape)
                    self.printer.setPaperSize(
                        QSizeF(self.composition.paperHeight(),
                               self.composition.paperWidth()),
                        QPrinter.Millimeter)
                else:
                    self.printer.setOrientation(QPrinter.Portrait)
                    self.printer.setPaperSize(
                        QSizeF(self.composition.paperWidth(),
                               self.composition.paperHeight()),
                        QPrinter.Millimeter)
                self.printer.setResolution(self.composition.printResolution())

                self.composition.beginPrint(self.printer)
                painter = QPainter(self.printer)
                if not len(atlas.featureFilterErrorString()) == 0:
                    QMessageBox.warning(
                        self, tr("Atlas processing error"),
                        tr("Feature filter parser error: %s" %
                           atlas.featureFilterErrorString()))
                    QApplication.restoreOverrideCursor()
                    return

                atlas.beginRender()
                progress = QProgressDialog(tr("Rendering maps..."),
                                           tr("Abort"), 0, atlas.numFeatures(),
                                           self)
                for featureI in range(0, atlas.numFeatures()):
                    progress.setValue(featureI + 1)
                    # process input events in order to allow cancelling
                    QCoreApplication.processEvents()
                    if progress.wasCanceled():
                        atlas.endRender()
                        break
                    if not atlas.prepareForFeature(featureI):
                        QMessageBox.warning(self, tr("Atlas processing error"),
                                            tr("Atlas processing error"))
                        progress.cancel()
                        QApplication.restoreOverrideCursor()
                        return

                    # start print on a new page if we're not on the first feature
                    if featureI > 0:
                        self.printer.newPage()
                    self.composition.doPrint(self.printer, painter)

                atlas.endRender()
                painter.end()
                QApplication.restoreOverrideCursor()

            elif self.ui.OutputTab.currentIndex() == 2:  # to Composer View
                # create new composer
                self.composition.setAtlasMode(QgsComposition.PreviewAtlas)
                composer.composerWindow().on_mActionAtlasPreview_triggered(
                    True)
                atlas.parameterChanged.emit()
                # Increase the reference count of the composer object
                # for not being garbage collected.
                # If not doing this composer would lost reference and qgis would crash
                # when referring to this composer object or at quit.
                ctypes.c_long.from_address(id(composer)).value += 1
        else:
            # if batch_plotting is False open a QgsComposerView with current map canvas
            cmap = self.composition.getComposerMapById(0)
            # set the new extent of composer map item
            newextent = self.iface.mapCanvas().mapRenderer().extent()
            currentextent = cmap.extent()
            canvas_ratio = newextent.width() / newextent.height()
            map_ratio = currentextent.width() / currentextent.height()
            if map_ratio < canvas_ratio:
                dh = newextent.width() / map_ratio - newextent.height()
                newextent.setYMinimum(newextent.yMinimum() - dh / 2)
                newextent.setYMaximum(newextent.yMaximum() + dh / 2)
            else:
                dw = map_ratio * newextent.height() - newextent.width()
                newextent.setXMinimum(newextent.xMinimum() - dw / 2)
                newextent.setXMaximum(newextent.xMaximum() + dw / 2)
            cmap.setNewExtent(newextent)
            # set the new scale of composer map item
            if scale > 0:
                cmap.setNewScale(scale)
            sc = cmap.scale()
            # set the grid interval according to the scale
            cmap.setGridIntervalX(sc / 10)
            cmap.setGridIntervalY(sc / 10)
            # Increase the reference count of the composer object
            # for not being garbage collected.
            # If not doing this composer would lost reference and qgis would crash
            # when referring to this composer object or at quit.
            ctypes.c_long.from_address(id(composer)).value += 1

        self.accept()
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.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()
        self.update_extent()

    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 will be used to determine the area for which '
            'you want data to be retrieved. You can adjust it manually using '
            'the bounding box options below.'))
        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):
        """ Update extent value in GUI based from value in map."""
        # Get the extent as [xmin, ymin, xmax, ymax]
        extent = viewport_geo_array(self.iface.mapCanvas())
        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 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 accept(self):
        """Do osm download and display it in QGIS."""
        error_dialog_title = self.tr('InaSAFE OpenStreetMap Downloader Error')

        # 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
            QMessageBox.warning(self, error_dialog_title, message)
            return

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

        try:
            self.save_state()
            self.require_directory()
            for feature_type in feature_types:
                self.download(feature_type)
                self.load_shapefile(feature_type)
            self.done(QDialog.Accepted)
        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
            QMessageBox.warning(self, error_dialog_title, str(exception))

            self.progress_dialog.cancel()

    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 = str(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
                QMessageBox.warning(
                    self,
                    self.tr('InaSAFE error'),
                    self.tr('Output directory can not be empty.'))
                raise CanceledImportDialogError()
        else:
            raise CanceledImportDialogError()

    def download(self, feature_type):
        """Download shapefiles from Linfiniti server.

        :param feature_type: What kind of features should be downloaded.
            Currently 'buildings' or 'roads' are supported.
        :type feature_type: 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
            )
        output_prefix = self.filename_prefix.text()
        if feature_type == 'buildings':
            url = "{url}?bbox={box}&qgis_version=2".format(
                url=self.buildings_url, box=box)
        else:
            url = "{url}?bbox={box}&qgis_version=2".format(
                url=self.roads_url, box=box)

        if output_prefix is not None:
            url += '&output_prefix=%s' % output_prefix

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

        # download and extract it
        self.fetch_zip(url, path)
        self.extract_zip(path, str(self.output_directory.text()))

        self.progress_dialog.done(QDialog.Accepted)

    def fetch_zip(self, url, output_path):
        """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

        :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()
        self.progress_dialog.setMaximum(100)
        self.progress_dialog.setValue(0)

        label_text = self.tr("Downloading shapefile")
        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
            raise DownloadError(error_message)

    @staticmethod
    def extract_zip(path, output_dir):
        """Extract all content of a .zip file from path to output_dir.

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

        :param output_dir: Output directory where the shp will be written to.
        :type output_dir: str

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

        import zipfile

        # extract all files...
        handle = open(path, 'rb')
        zip_file = zipfile.ZipFile(handle)
        for name in zip_file.namelist():
            output_path = os.path.join(output_dir, name)
            output_file = open(output_path, 'wb')
            output_file.write(zip_file.read(name))
            output_file.close()

        handle.close()

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

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

        :raises: ImportDialogError - when buildings.shp not exist
        """
        output_prefix = self.filename_prefix.text()
        path = str(self.output_directory.text())
        path = os.path.join(path, '%s%s.shp' % (output_prefix, feature_type))

        if not os.path.exists(path):
            message = self.tr(
                "%s don't exist. The server doesn't have any data.")
            raise ImportDialogError(message)

        self.iface.addVectorLayer(path, feature_type, 'ogr')
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()
Example #13
0
class OsmDownloader(QDialog, Ui_OsmDownloaderBase):
    """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.url = "http://osm.linfiniti.com/buildings-shp"

        # creating progress dialog for download
        self.progressDialog = QProgressDialog(self)
        self.progressDialog.setAutoClose(False)
        myTitle = self.tr("InaSAFE OpenStreetMap Downloader")
        self.progressDialog.setWindowTitle(myTitle)

        self.show_info()

        self.network_manager = QNetworkAccessManager(self)
        self.restore_state()
        self.update_extent()

    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\') 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('Use QGIS to zoom in to the area for which you want building data '
               'to be retrieved.'))
        tips.add(
            self.
            tr('Check the output directory is correct. Note that the saved '
               'dataset will be called buildings.shp (and its associated files).'
               ))
        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 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.webView.setHtml(string)

    def restore_state(self):
        """ Read last state of GUI from configuration file."""
        mySetting = QSettings()
        self.outDir.setText(mySetting.value('directory').toString())

    def save_state(self):
        """ Store current state of GUI to configuration file """
        mySetting = QSettings()
        mySetting.setValue('directory', self.outDir.text())

    def update_extent(self):
        """ Update extent value in GUI based from value in map."""
        myExtent = self.iface.mapCanvas().extent()
        self.minLongitude.setText(str(myExtent.xMinimum()))
        self.minLatitude.setText(str(myExtent.yMinimum()))
        self.maxLongitude.setText(str(myExtent.xMaximum()))
        self.maxLatitude.setText(str(myExtent.yMaximum()))

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

    def accept(self):
        """Do osm download and display it in QGIS."""

        try:
            self.save_state()

            self.require_directory()
            self.download()
            self.load_shapefile()
            self.done(QDialog.Accepted)
        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as myEx:
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QMessageBox.warning(
                self, self.tr("InaSAFE OpenStreetMap downloader error"),
                str(myEx))

            self.progressDialog.cancel()

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

        myDir = str(self.outDir.text())

        if os.path.exists(myDir):
            return

        myTitle = self.tr("Directory %1 not exist").arg(myDir)
        myQuestion = self.tr(
            "Directory %1 not exist. Are you want to create it?").arg(myDir)
        # noinspection PyCallByClass,PyTypeChecker
        myAnswer = QMessageBox.question(self, myTitle, myQuestion,
                                        QMessageBox.Yes | QMessageBox.No)

        if myAnswer == QMessageBox.Yes:
            os.makedirs(myDir)
        else:
            raise CanceledImportDialogError()

    def download(self):
        """Download shapefiles from Linfinti server.

        :raises: ImportDialogError, CanceledImportDialogError
        """

        ## preparing necessary data
        myMinLng = str(self.minLongitude.text())
        myMinLat = str(self.minLatitude.text())
        myMaxLng = str(self.maxLongitude.text())
        myMaxLat = str(self.maxLatitude.text())

        myCoordinate = "{myMinLng},{myMinLat},{myMaxLng},{myMaxLat}".format(
            myMinLng=myMinLng,
            myMinLat=myMinLat,
            myMaxLng=myMaxLng,
            myMaxLat=myMaxLat)

        myShapeUrl = "{url}?bbox={myCoordinate}".format(
            url=self.url, myCoordinate=myCoordinate)

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

        # download and extract it
        self.fetch_zip(myShapeUrl, myFilePath)
        print myFilePath
        print str(self.outDir.text())
        self.extract_zip(myFilePath, str(self.outDir.text()))

        self.progressDialog.done(QDialog.Accepted)

    def fetch_zip(self, url, output_path):
        """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

        :raises: ImportDialogError - when network error occurred
        """

        self.progressDialog.show()
        self.progressDialog.setMaximum(100)
        self.progressDialog.setValue(0)

        # myLabelText = "Begin downloading shapefile from " \
        #               + "%1 ..."
        # self.progressDialog.setLabelText(self.tr(myLabelText).arg(url))
        myLabelText = self.tr("Downloading shapefile")
        self.progressDialog.setLabelText(myLabelText)

        myResult = download_url(self.network_manager, url, output_path,
                                self.progressDialog)

        if myResult is not True:
            _, myErrorMessage = myResult
            raise ImportDialogError(myErrorMessage)

    def extract_zip(self, path, output_dir):
        """Extract all content of a .zip file from path to output_dir.

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

        :param output_dir: Output directory where the shp will be written to.
        :type output_dir: str

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

        import zipfile

        ## extract all files...
        myHandle = open(path, 'rb')
        myZip = zipfile.ZipFile(myHandle)
        for myName in myZip.namelist():
            myOutPath = os.path.join(output_dir, myName)
            myOutFile = open(myOutPath, 'wb')
            myOutFile.write(myZip.read(myName))
            myOutFile.close()

        myHandle.close()

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

        :raises: ImportDialogError - when buildings.shp not exist
        """

        myDir = str(self.outDir.text())
        myPath = os.path.join(myDir, 'buildings.shp')

        if not os.path.exists(myPath):
            myMessage = self.tr(
                "%s don't exist. The server don't have buildings data.")
            raise ImportDialogError(myMessage)

        self.iface.addVectorLayer(myPath, 'buildings', 'ogr')
    def process(self):
        if not os.path.exists(self.outputDir):
            qm = QtGui.QMessageBox
            ret = qm.question(self, 'Create Directory?',
                              "Output mask directory does not exist. Create?",
                              qm.Yes | qm.No)

            if ret == qm.Yes:
                os.makedirs(self.outputDir)

        if self.cbCropAlbedo.isChecked():
            if not os.path.exists(os.path.join(self.outputDir,
                                               "CroppedAlbedo")):
                os.makedirs(os.path.join(self.outputDir, "CroppedAlbedo"))

        if self.cbCropGrayscale.isChecked():
            if not os.path.exists(
                    os.path.join(self.outputDir, "CroppedGrayscale")):
                os.makedirs(os.path.join(self.outputDir, "CroppedGrayscale"))

        if self.cbCropComposite.isChecked():
            if not os.path.exists(
                    os.path.join(self.outputDir, "CroppedComposite")):
                os.makedirs(os.path.join(self.outputDir, "CroppedComposite"))

        plantNos = [int(i) for i in self.edtPlants.text().split(',')]
        if not plantNos:
            qm.information(
                self, 'Error',
                'Please enter the plants to process separated by commas')
        startFolder = os.path.normpath(
            os.path.join(self.rootDir, str(self.cmbStartSess.currentText())))
        endFolder = os.path.normpath(
            os.path.join(self.rootDir, str(self.cmbEndSess.currentText())))

        self.segParam1 = self.edtSegParam1.text()
        self.segParam2 = self.edtSegParam2.text()
        self.segParam3 = self.edtSegParam3.text()

        d = glob.glob(os.path.join(self.rootDir, self.getLSType()))

        d.sort()
        d = [os.path.normpath(p) for p in d]

        if startFolder:
            startIdx = d.index(startFolder)
            endIdx = d.index(endFolder)

            d = d[startIdx:endIdx + 1]

        total2Proc = len(d)

        progress = QProgressDialog("Creating Masks...", "Abort", 0, total2Proc,
                                   self)
        progress.setWindowTitle("Mask Generator")
        progress.setWindowModality(QtCore.Qt.WindowModal)
        try:
            for n, pth in enumerate(d):
                progress.setValue(n)
                if progress.wasCanceled():
                    break
                for p in plantNos:
                    if progress.wasCanceled():
                        break
                    print('Processing Plant: {}, {}'.format(p, pth))

                    A, z, snIm, shadowIm = loadPlant(pth, p, self.roiFile)
                    shadowIm, minImg, maxImg = ps.shadowImageFromPth(pth)
                    rois = np.loadtxt(self.roiFile, dtype=np.uint16)
                    roi = rois[p]
                    minImg = minImg[roi[1]:roi[1] + roi[3],
                                    roi[0]:roi[0] + roi[2]]

                    params = {
                        'im': A,
                        'minImg': minImg,
                        'T': int(self.segParam1),
                        'minSize': int(self.segParam2),
                        'diskSize': int(self.segParam3)
                    }
                    segmenter = MaskSegmenterBasic(params)
                    mask = segmenter.segmentImage()
                    mask.dtype = 'uint8'

                    data = np.load(
                        os.path.join(pth,
                                     "SNZShadowImAndAlbedo_adaptiveLS.npz"))
                    snIm = data['snIm']
                    A = data['A']
                    A = self.normaliseRange(A)
                    snIm = self.normaliseRange(snIm)

                    im = glob.glob(os.path.join(pth, '*im[0-9].bmp'))
                    ims = []
                    for i, idx in enumerate(im):
                        ims.append(cv2.imread(idx, 0))

                    arr = np.zeros((np.size(ims[0], 1), np.size(ims[0], 0)),
                                   np.float)
                    for im in ims:
                        arr = arr + im / (i + 1)

                    composite = np.zeros((np.size(ims[0],
                                                  1), np.size(ims[0], 0), 3))
                    composite[..., 0] = snIm[:, :, 0]
                    composite[..., 1] = snIm[:, :, 1]
                    composite[..., 2] = A

                    arr = self.normaliseRange(arr)

                    fname = '{}_{}_mask.png'.format(p, os.path.basename(pth))
                    cv2.imwrite(os.path.join(self.outputDir, fname),
                                mask * 255)

                    if self.cbCropAlbedo.isChecked():
                        fname = '{}_{}_albedo.png'.format(
                            p, os.path.basename(pth))
                        cv2.imwrite(
                            os.path.join(
                                os.path.join(self.outputDir, "CroppedAlbedo"),
                                fname),
                            self.normaliseRange(A)[roi[1]:roi[1] + roi[3],
                                                   roi[0]:roi[0] + roi[2]])

                    if self.cbCropGrayscale.isChecked():
                        fname = '{}_{}_grayscale.png'.format(
                            p, os.path.basename(pth))
                        cv2.imwrite(
                            os.path.join(
                                os.path.join(self.outputDir,
                                             "CroppedGrayscale"), fname),
                            arr[roi[1]:roi[1] + roi[3],
                                roi[0]:roi[0] + roi[2]])

                    if self.cbCropComposite.isChecked():
                        fname = '{}_{}_composite.png'.format(
                            p, os.path.basename(pth))
                        cv2.imwrite(
                            os.path.join(
                                os.path.join(self.outputDir,
                                             "CroppedComposite"), fname),
                            composite[roi[1]:roi[1] + roi[3],
                                      roi[0]:roi[0] + roi[2]])

            progress.setValue(total2Proc)
            qm = QtGui.QMessageBox
            qm.information(self, 'Completed',
                           'Masks can be found in {}.'.format(self.outputDir))
        except Exception as e:
            progress.cancel()
            qm = QtGui.QMessageBox
            qm.information(self, 'Error', '{}'.format(e))
            raise e
Example #15
0
class ImportDialog(QDialog, Ui_ImportDialogBase):

    def __init__(self, theParent=None, theIface=None):
        """Constructor for import dialog.

        Args:
           * theParent - Optional widget to use as parent
           * theIface - an instance of QGisInterface
        Returns:
           not applicable
        Raises:
           no exceptions explicitly raised
        """
        QDialog.__init__(self, theParent)
        self.parent = theParent
        self.setupUi(self)

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

        self.iface = theIface
        self.url = "http://osm.linfiniti.com/buildings-shp"

        ## region coordinate: (latitude, longtitude, zoom_level)
        self.regionExtent = {
            '0': [18.87685, -71.493, 6],     # haiti
            '1': [-2.5436300, 116.8887, 3],  # indonesia
            '2': [1.22449, 15.40999, 2],     # africa
            '3': [34.05, 56.55, 3],          # middle east
            '4': [12.98855, 121.7166, 4],    # philipine
        }

        # creating progress dialog for download
        self.progressDialog = QProgressDialog(self)
        self.progressDialog.setAutoClose(False)
        myTitle = self.tr("InaSAFE OpenStreetMap Downloader")
        self.progressDialog.setWindowTitle(myTitle)

        ## set map parameter based on placeholder self.map widget
        theMap = InasafeLightMaps(self.gbxMap)
        theMap.setGeometry(self.map.geometry())
        theMap.setSizePolicy(self.map.sizePolicy())
        self.map = theMap

        self.nam = QNetworkAccessManager(self)

        self.setupOptions()

        self.restoreState()

        self.cbxRegion.currentIndexChanged.connect(self.regionChanged)
        self.map.m_normalMap.updated.connect(self.updateExtent)

    def regionChanged(self, theIndex):
        """ Slot that called when region combo box changed.
        Params:
            theIndex - index of combo box
        """
        myRegionIndex = str(self.cbxRegion.itemData(theIndex).toString())
        myCenter = self.regionExtent[myRegionIndex]
        self.map.setCenter(myCenter[0], myCenter[1], myCenter[2])

    # pylint: disable=W0613
    def resizeEvent(self, theEvent):
        """ Function that called when resize event occurred.
        Params:
            theEvent - QEvent instance. Not used.
        """
        self.map.resize(self.gbxMap.width() - 30, self.gbxMap.height() - 30)
    # pylint: disable=W0613

    def setupOptions(self):
        """ Set the content of combo box for region and preset """
        self.cbxRegion.insertItem(0, 'Indonesia', 1)
        self.cbxRegion.insertItem(1, 'Africa', 2)
        self.cbxRegion.insertItem(2, 'Philippines', 4)
        self.cbxRegion.insertItem(3, 'Central Asia/Middle East', 3)
        self.cbxRegion.insertItem(4, 'Haiti', 0)

        self.cbxPreset.insertItem(0, self.tr('Buildings'), 'building')
        self.cbxPreset.insertItem(0, self.tr('Highway'), 'highway')

    def restoreState(self):
        """ Read last state of GUI from configuration file """
        mySetting = QSettings()

        myRegion = mySetting.value('region').toInt()
        if myRegion[1] is True:
            self.cbxRegion.setCurrentIndex(myRegion[0])

        myPreset = mySetting.value('preset').toInt()
        if myPreset[1] is True:
            self.cbxPreset.setCurrentIndex(myPreset[0])

        self.outDir.setText(mySetting.value('directory').toString())

        myZoomLevel = mySetting.value('zoom_level').toInt()
        myLatitude = mySetting.value('latitude').toDouble()
        myLongitude = mySetting.value('longitude').toDouble()

        if myZoomLevel[1] is True:
            self.map.setCenter(myLatitude[0], myLongitude[0], myZoomLevel[0])
        else:
            # just set to indonesia extent
            myCenter = self.regionExtent['1']
            self.map.setCenter(myCenter[0], myCenter[1], myCenter[2])

    def saveState(self):
        """ Store current state of GUI to configuration file """
        mySetting = QSettings()
        mySetting.setValue('region', self.cbxRegion.currentIndex())
        mySetting.setValue('preset', self.cbxPreset.currentIndex())
        mySetting.setValue('directory', self.outDir.text())

        mySetting.setValue('zoom_level', self.map.getZoomLevel())
        myCenter = self.map.getCenter()
        mySetting.setValue('latitude', myCenter[0])
        mySetting.setValue('longitude', myCenter[1])

    def updateExtent(self):
        """ Update extent value in GUI based from value in map widget"""
        myExtent = self.map.getExtent()
        self.minLongitude.setText(str(myExtent[1]))
        self.minLatitude.setText(str(myExtent[0]))
        self.maxLongitude.setText(str(myExtent[3]))
        self.maxLatitude.setText(str(myExtent[2]))

    @pyqtSignature('')  # prevents actions being handled twice
    def on_pBtnDir_clicked(self):
        """ Show a dialog to choose directory """
        self.outDir.setText(QFileDialog.getExistingDirectory(
            self, self.tr("Select Directory")))

    def accept(self):
        """ Do import process """

        try:
            self.saveState()

            self.ensureDirExist()
            self.doImport()
            self.loadShapeFile()
            self.done(QDialog.Accepted)
        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as myEx:
            QMessageBox.warning(
                self,
                self.tr("InaSAFE OpenStreetMap Downloader Error"),
                str(myEx))

            self.progressDialog.cancel()

    def ensureDirExist(self):
        """
        Ensure directory path entered in dialog exist.
        When the path is not exist, this function will
        ask the user if he want to create it or not.

        Raises:
            CanceledImportDialogError - when user choose 'No'
                        in question dialog for creating directory.
        """

        myDir = str(self.outDir.text())

        if os.path.exists(myDir):
            return

        myTitle = self.tr("Directory %1 not exist").arg(myDir)
        myQuestion = self.tr(
            "Directory %1 not exist. Are you want to create it?"
        ).arg(myDir)
        myAnswer = QMessageBox.question(
            self, myTitle,
            myQuestion, QMessageBox.Yes | QMessageBox.No)

        if myAnswer == QMessageBox.Yes:
            os.makedirs(myDir)
        else:
            raise CanceledImportDialogError()

    def doImport(self):
        """
        Import shape files from Linfinti.
        Raises:
            * ImportDialogError - when network error occurred
            * CanceledImportDialogError - when user press cancel button
        """

        ## preparing necessary data
        myMinLng = str(self.minLongitude.text())
        myMinLat = str(self.minLatitude.text())
        myMaxLng = str(self.maxLongitude.text())
        myMaxLat = str(self.maxLatitude.text())

        myCurrentIndex = self.cbxPreset.currentIndex()
        myType = str(self.cbxPreset.itemData(myCurrentIndex).toString())

        myCoordinate = "{myMinLng},{myMinLat},{myMaxLng},{myMaxLat}".format(
            myMinLng=myMinLng,
            myMinLat=myMinLat,
            myMaxLng=myMaxLng,
            myMaxLat=myMaxLat
        )

        myShapeUrl = "{url}?bbox={myCoordinate}&obj={type}".format(
            url=self.url,
            myCoordinate=myCoordinate,
            type=myType
        )

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

        # download and extract it
        self.downloadShapeFile(myShapeUrl, myFilePath)
        self.extractZip(myFilePath, str(self.outDir.text()))

        self.progressDialog.done(QDialog.Accepted)

    def downloadShapeFile(self, theUrl, theOutput):
        """
        Download shape file from theUrl and write to theOutput.
        Params:
            * theUrl - URL of shape file
            * theOutput - path of output file
        Raises:
            * ImportDialogError - when network error occurred
        """

        self.progressDialog.show()
        self.progressDialog.setMaximum(100)
        self.progressDialog.setValue(0)

        # myLabelText = "Begin downloading shapefile from " \
        #               + "%1 ..."
        # self.progressDialog.setLabelText(self.tr(myLabelText).arg(theUrl))
        myLabelText = self.tr("Begin downloading shapefile")
        self.progressDialog.setLabelText(myLabelText)

        myResult = httpDownload(self.nam, theUrl, theOutput,
                                self.progressDialog)

        if myResult is not True:
            _, myErrorMessage = myResult
            raise ImportDialogError(myErrorMessage)

    def extractZip(self, thePath, theOutDir):
        """
        Extract all content of zip file from thePath to theOutDir.
        Args:
           * thePath - the path of zip file
           * theOutDir - output directory
        Raises:
            IOError - when cannot open thePath or theOutDir is not exist.
        """

        import zipfile

        ## extract all files...
        myHandle = open(thePath, 'rb')
        myZip = zipfile.ZipFile(myHandle)
        for myName in myZip.namelist():
            myOutPath = os.path.join(theOutDir, myName)
            myOutFile = open(myOutPath, 'wb')
            myOutFile.write(myZip.read(myName))
            myOutFile.close()

        myHandle.close()

    def loadShapeFile(self):
        """
        Load downloaded shape file to QGIS Main Window.

        Raises:
            ImportDialogError - when buildings.shp not exist
        """

        myDir = str(self.outDir.text())
        myPath = os.path.join(myDir, 'buildings.shp')

        if not os.path.exists(myPath):
            myMessage = self.tr(
                "%s don't exist. The server don't have buildings data."
            )
            raise ImportDialogError(myMessage)

        self.iface.addVectorLayer(myPath, 'buildings', 'ogr')
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()
Example #17
0
class ImportDialog(QDialog, Ui_ImportDialogBase):
    def __init__(self, theParent=None, theIface=None):
        """Constructor for import dialog.

        Args:
           * theParent - Optional widget to use as parent
           * theIface - an instance of QGisInterface
        Returns:
           not applicable
        Raises:
           no exceptions explicitly raised
        """
        QDialog.__init__(self, theParent)
        self.parent = theParent
        self.setupUi(self)

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

        self.iface = theIface
        self.url = "http://osm.linfiniti.com/buildings-shp"

        ## region coordinate: (latitude, longtitude, zoom_level)
        self.regionExtent = {
            '0': [18.87685, -71.493, 6],  # haiti
            '1': [-2.5436300, 116.8887, 3],  # indonesia
            '2': [1.22449, 15.40999, 2],  # africa
            '3': [34.05, 56.55, 3],  # middle east
            '4': [12.98855, 121.7166, 4],  # philipine
        }

        # creating progress dialog for download
        self.progressDialog = QProgressDialog(self)
        self.progressDialog.setAutoClose(False)
        myTitle = self.tr("InaSAFE OpenStreetMap Downloader")
        self.progressDialog.setWindowTitle(myTitle)

        ## set map parameter based on placeholder self.map widget
        theMap = InasafeLightMaps(self.gbxMap)
        theMap.setGeometry(self.map.geometry())
        theMap.setSizePolicy(self.map.sizePolicy())
        self.map = theMap

        self.nam = QNetworkAccessManager(self)

        self.setupOptions()

        self.restoreState()

        self.cbxRegion.currentIndexChanged.connect(self.regionChanged)
        self.map.m_normalMap.updated.connect(self.updateExtent)

    def regionChanged(self, theIndex):
        """ Slot that called when region combo box changed.
        Params:
            theIndex - index of combo box
        """
        myRegionIndex = str(self.cbxRegion.itemData(theIndex).toString())
        myCenter = self.regionExtent[myRegionIndex]
        self.map.setCenter(myCenter[0], myCenter[1], myCenter[2])

    # pylint: disable=W0613
    def resizeEvent(self, theEvent):
        """ Function that called when resize event occurred.
        Params:
            theEvent - QEvent instance. Not used.
        """
        self.map.resize(self.gbxMap.width() - 30, self.gbxMap.height() - 30)

    # pylint: disable=W0613

    def setupOptions(self):
        """ Set the content of combo box for region and preset """
        self.cbxRegion.insertItem(0, 'Indonesia', 1)
        self.cbxRegion.insertItem(1, 'Africa', 2)
        self.cbxRegion.insertItem(2, 'Philippines', 4)
        self.cbxRegion.insertItem(3, 'Central Asia/Middle East', 3)
        self.cbxRegion.insertItem(4, 'Haiti', 0)

        self.cbxPreset.insertItem(0, self.tr('Buildings'), 'building')
        self.cbxPreset.insertItem(0, self.tr('Highway'), 'highway')

    def restoreState(self):
        """ Read last state of GUI from configuration file """
        mySetting = QSettings()

        myRegion = mySetting.value('region').toInt()
        if myRegion[1] is True:
            self.cbxRegion.setCurrentIndex(myRegion[0])

        myPreset = mySetting.value('preset').toInt()
        if myPreset[1] is True:
            self.cbxPreset.setCurrentIndex(myPreset[0])

        self.outDir.setText(mySetting.value('directory').toString())

        myZoomLevel = mySetting.value('zoom_level').toInt()
        myLatitude = mySetting.value('latitude').toDouble()
        myLongitude = mySetting.value('longitude').toDouble()

        if myZoomLevel[1] is True:
            self.map.setCenter(myLatitude[0], myLongitude[0], myZoomLevel[0])
        else:
            # just set to indonesia extent
            myCenter = self.regionExtent['1']
            self.map.setCenter(myCenter[0], myCenter[1], myCenter[2])

    def saveState(self):
        """ Store current state of GUI to configuration file """
        mySetting = QSettings()
        mySetting.setValue('region', self.cbxRegion.currentIndex())
        mySetting.setValue('preset', self.cbxPreset.currentIndex())
        mySetting.setValue('directory', self.outDir.text())

        mySetting.setValue('zoom_level', self.map.getZoomLevel())
        myCenter = self.map.getCenter()
        mySetting.setValue('latitude', myCenter[0])
        mySetting.setValue('longitude', myCenter[1])

    def updateExtent(self):
        """ Update extent value in GUI based from value in map widget"""
        myExtent = self.map.getExtent()
        self.minLongitude.setText(str(myExtent[1]))
        self.minLatitude.setText(str(myExtent[0]))
        self.maxLongitude.setText(str(myExtent[3]))
        self.maxLatitude.setText(str(myExtent[2]))

    @pyqtSignature('')  # prevents actions being handled twice
    def on_pBtnDir_clicked(self):
        """ Show a dialog to choose directory """
        self.outDir.setText(
            QFileDialog.getExistingDirectory(self,
                                             self.tr("Select Directory")))

    def accept(self):
        """ Do import process """

        try:
            self.saveState()

            self.ensureDirExist()
            self.doImport()
            self.loadShapeFile()
            self.done(QDialog.Accepted)
        except CanceledImportDialogError:
            # don't show anything because this exception raised
            # when user canceling the import process directly
            pass
        except Exception as myEx:
            QMessageBox.warning(
                self, self.tr("InaSAFE OpenStreetMap Downloader Error"),
                str(myEx))

            self.progressDialog.cancel()

    def ensureDirExist(self):
        """
        Ensure directory path entered in dialog exist.
        When the path is not exist, this function will
        ask the user if he want to create it or not.

        Raises:
            CanceledImportDialogError - when user choose 'No'
                        in question dialog for creating directory.
        """

        myDir = str(self.outDir.text())

        if os.path.exists(myDir):
            return

        myTitle = self.tr("Directory %1 not exist").arg(myDir)
        myQuestion = self.tr(
            "Directory %1 not exist. Are you want to create it?").arg(myDir)
        myAnswer = QMessageBox.question(self, myTitle, myQuestion,
                                        QMessageBox.Yes | QMessageBox.No)

        if myAnswer == QMessageBox.Yes:
            os.makedirs(myDir)
        else:
            raise CanceledImportDialogError()

    def doImport(self):
        """
        Import shape files from Linfinti.
        Raises:
            * ImportDialogError - when network error occurred
            * CanceledImportDialogError - when user press cancel button
        """

        ## preparing necessary data
        myMinLng = str(self.minLongitude.text())
        myMinLat = str(self.minLatitude.text())
        myMaxLng = str(self.maxLongitude.text())
        myMaxLat = str(self.maxLatitude.text())

        myCurrentIndex = self.cbxPreset.currentIndex()
        myType = str(self.cbxPreset.itemData(myCurrentIndex).toString())

        myCoordinate = "{myMinLng},{myMinLat},{myMaxLng},{myMaxLat}".format(
            myMinLng=myMinLng,
            myMinLat=myMinLat,
            myMaxLng=myMaxLng,
            myMaxLat=myMaxLat)

        myShapeUrl = "{url}?bbox={myCoordinate}&obj={type}".format(
            url=self.url, myCoordinate=myCoordinate, type=myType)

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

        # download and extract it
        self.downloadShapeFile(myShapeUrl, myFilePath)
        self.extractZip(myFilePath, str(self.outDir.text()))

        self.progressDialog.done(QDialog.Accepted)

    def downloadShapeFile(self, theUrl, theOutput):
        """
        Download shape file from theUrl and write to theOutput.
        Params:
            * theUrl - URL of shape file
            * theOutput - path of output file
        Raises:
            * ImportDialogError - when network error occurred
        """

        self.progressDialog.show()
        self.progressDialog.setMaximum(100)
        self.progressDialog.setValue(0)

        # myLabelText = "Begin downloading shapefile from " \
        #               + "%1 ..."
        # self.progressDialog.setLabelText(self.tr(myLabelText).arg(theUrl))
        myLabelText = self.tr("Begin downloading shapefile")
        self.progressDialog.setLabelText(myLabelText)

        myResult = httpDownload(self.nam, theUrl, theOutput,
                                self.progressDialog)

        if myResult is not True:
            _, myErrorMessage = myResult
            raise ImportDialogError(myErrorMessage)

    def extractZip(self, thePath, theOutDir):
        """
        Extract all content of zip file from thePath to theOutDir.
        Args:
           * thePath - the path of zip file
           * theOutDir - output directory
        Raises:
            IOError - when cannot open thePath or theOutDir is not exist.
        """

        import zipfile

        ## extract all files...
        myHandle = open(thePath, 'rb')
        myZip = zipfile.ZipFile(myHandle)
        for myName in myZip.namelist():
            myOutPath = os.path.join(theOutDir, myName)
            myOutFile = open(myOutPath, 'wb')
            myOutFile.write(myZip.read(myName))
            myOutFile.close()

        myHandle.close()

    def loadShapeFile(self):
        """
        Load downloaded shape file to QGIS Main Window.

        Raises:
            ImportDialogError - when buildings.shp not exist
        """

        myDir = str(self.outDir.text())
        myPath = os.path.join(myDir, 'buildings.shp')

        if not os.path.exists(myPath):
            myMessage = self.tr(
                "%s don't exist. The server don't have buildings data.")
            raise ImportDialogError(myMessage)

        self.iface.addVectorLayer(myPath, 'buildings', 'ogr')