class Progress: def __init__(self, parent): self.parent = parent self.dialog = None self.application = None def started(self, title): self.dialog = QProgressDialog(title, "Stop", 0, 0, self.parent) self.dialog.setCancelButton(None) self.dialog.show() QCoreApplication.processEvents(QEventLoop.AllEvents) def progress(self, msg, percent): self.dialog.setLabelText(msg) # otherwise KProgressDialog automatically closes itself, sigh if percent < 100: self.dialog.setValue(percent) QCoreApplication.processEvents(QEventLoop.AllEvents) def finished(self): if self.dialog: self.dialog.done(0) self.dialog = None
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()
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')
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')
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.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()
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')
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')