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