def _get_sync(self, qurl: QUrl, timeout: int = 20000) -> Reply: ''' synchronous GET-request ''' request = QNetworkRequest(qurl) ## newer versions of QGIS (3.6+) support synchronous requests #if hasattr(self._manager, 'blockingGet'): #reply = self._manager.blockingGet(request, forceRefresh=True) ## use blocking event loop for older versions #else: loop = QEventLoop() timer = QTimer() timer.setSingleShot(True) # reply or timeout break event loop, whoever comes first timer.timeout.connect(loop.quit) reply = self._manager.get(request) reply.finished.connect(loop.quit) timer.start(timeout) # start blocking loop loop.exec() loop.deleteLater() if not timer.isActive(): reply.deleteLater() raise ConnectionError('Timeout') timer.stop() #if reply.error(): #self.error.emit(reply.errorString()) #raise ConnectionError(reply.errorString()) res = Reply(reply) self.finished.emit(res) return res
def _load_layer(self, loader, conn_info, meta, **kw_start): lst_layer = list() batch = 100 # kw_start.get("limit",30000) for i in range(6): timer = QTimer() timer.timeout.connect(lambda: self.check_loader(loader,batch)) if not i: loader.start(conn_info, meta, **kw_start) else: loader.restart(conn_info, meta, **kw_start) timer.start(10) try: self._wait_async() except AllErrorsDuringTest as e: lst_err = e.args[0] self.assertIsInstance(lst_err[0], ManualInterrupt) finally: timer.stop() lst_layer.append(loader.layer) # with self.assertRaises(AllErrorsDuringTest, msg="stopping loader should emit error") as cm: # self._wait_async() # lst_err = cm.exception.args[0] # self.assertIsInstance(lst_err[0], ManualInterrupt) return lst_layer
class FileGPSService(GPSService): def __init__(self, filename): super(FileGPSService, self).__init__() self.file = None self.timer = QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self._read_from_file) self.filename = filename def connectGPS(self, portname): # Normally the portname is passed but we will take a filename # because it's a fake GPS service if not self.isConnected: self.file = open(self.filename, "r") self.timer.start() self.isConnected = True def _read_from_file(self): line = self.file.readline() if not line: self.file.seek(0) line = self.file.readline() self.parse_data(line) def disconnectGPS(self): if self.file: self.file.close() self.timer.stop() self.file = None self.gpsdisconnected.emit()
class ToolTipClass(QgsMapTool): def __init__(self, canvas, ms): # msはミリ秒 QgsMapTool.__init__(self, canvas) self.canvas = canvas self.ms = ms # canvasMoveEventで設定した秒数(msで設定)経過したら呼ばれるメソッドを設定 self.timerMapTips = QTimer(canvas) self.timerMapTips.timeout.connect(self.showMapTip) def canvasMoveEvent(self, event): QToolTip.hideText() self.timerMapTips.start(self.ms) def showMapTip(self): self.timerMapTips.stop() # 表示する値を設定する。 mappos = self.toMapCoordinates(self.canvas.mouseLastXY()) value = mappos if value == None: return text = str(value) QToolTip.showText(self.canvas.mapToGlobal(self.canvas.mouseLastXY()), text, self.canvas) def deactivate(self): self.timerMapTips.timeout.disconnect(self.showMapTip)
def test_link_feature(self): """ Check if an existing feature can be linked """ wrapper = self.createWrapper(self.vl_a, '"name"=\'Douglas Adams\'') # NOQA f = QgsFeature(self.vl_b.fields()) f.setAttributes([self.vl_b.dataProvider().defaultValueClause(0), 'The Hitchhiker\'s Guide to the Galaxy']) self.vl_b.addFeature(f) def choose_linked_feature(): dlg = QApplication.activeModalWidget() dlg.setSelectedFeatures([f.id()]) dlg.accept() btn = self.widget.findChild(QToolButton, 'mLinkFeatureButton') timer = QTimer() timer.setSingleShot(True) timer.setInterval(0) # will run in the event loop as soon as it's processed when the dialog is opened timer.timeout.connect(choose_linked_feature) timer.start() btn.click() # magically the above code selects the feature here... link_feature = next(self.vl_link.getFeatures(QgsFeatureRequest().setFilterExpression('"fk_book"={}'.format(f[0])))) self.assertIsNotNone(link_feature[0]) self.assertEqual(self.table_view.model().rowCount(), 1)
def test_link_feature(self): """ Check if an existing feature can be linked """ wrapper = self.createWrapper(self.vl_a, '"name"=\'Douglas Adams\'') # NOQA f = QgsFeature(self.vl_b.fields()) f.setAttributes([self.vl_b.dataProvider().defaultValueClause(0), 'The Hitchhiker\'s Guide to the Galaxy']) self.vl_b.addFeature(f) def choose_linked_feature(): dlg = QApplication.activeModalWidget() dlg.setSelectedFeatures([f.id()]) dlg.accept() btn = self.widget.findChild(QToolButton, 'mLinkFeatureButton') timer = QTimer() timer.setSingleShot(True) timer.setInterval(0) # will run in the event loop as soon as it's processed when the dialog is opened timer.timeout.connect(choose_linked_feature) timer.start() btn.click() # magically the above code selects the feature here... link_feature = next(self.vl_link.getFeatures(QgsFeatureRequest().setFilterExpression('"fk_book"={}'.format(f[0])))) self.assertIsNotNone(link_feature[0]) self.assertEqual(self.table_view.model().rowCount(), 1)
class ShowConsoleDialog(QDialog, FORM_CLASS): """ Non-modal dialog to display the console log of a OQ-Engine calculation """ def __init__(self, driver_dialog, calc_id): QDialog.__init__(self) # Set up the user interface from Designer. self.setupUi(self) self.driver_dialog = driver_dialog self.calc_id = calc_id # when re-opening the dialog for a calculation, display the log from # the beginning self.driver_dialog.calc_log_line[calc_id] = 0 self.timer = QTimer() self.timer.timeout.connect(self.refresh_calc_log) self.timer.start(3000) # refresh time in milliseconds # show the log before the first iteration of the timer self.refresh_calc_log() def refresh_calc_log(self): calc_status = self.driver_dialog.get_calc_status(self.calc_id) if calc_status is None: self.reject() return if calc_status['status'] in ('complete', 'failed'): self.timer.stop() calc_log = self.driver_dialog.get_calc_log(self.calc_id) if calc_log and not isinstance(calc_log, Exception): self.text_browser.append(calc_log) def reject(self): self.timer.stop() super().reject()
def _check_path_exists(self, path, text_box): # Validates if the specified folder exists dir = QDir() if not dir.exists(path): msg = self.tr("'{0}' directory does not exist.".format(path)) self.notif_bar.insertErrorNotification(msg) # Highlight textbox control text_box.setStyleSheet(INVALIDATESTYLESHEET) timer = QTimer(self) # Sync interval with that of the notification bar timer.setInterval(self.notif_bar.interval) timer.setSingleShot(True) # Remove previous connected slots (if any) receivers = timer.receivers(QTimer.timeout) if receivers > 0: self._timer.timeout.disconnect() timer.start() timer.timeout.connect(lambda: self._restore_stylesheet( text_box) ) return False return True
def update_referecedata_cache_model(self, filter_models, type): # updates the model and waits for the end loop = QEventLoop() self.ilireferencedatacache.model_refreshed.connect(lambda: loop.quit()) timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(lambda: loop.quit()) timer.start(10000) self.refresh_referencedata_cache(filter_models, type) loop.exec() return self.ilireferencedatacache.model
class MapCanvasEffects(): def __init__(self): self.project = QgsProject().instance() self.canvas = QgsUtils.iface.mapCanvas() self.timer = QTimer( self.canvas ) self.flash = None def highlight(self, layer, geometry): def getFlash(): h = QgsHighlight( self.canvas, geometry, layer ) h.setColor( QColor( 255, 0, 0, 255 ) ) h.setFillColor( QColor( 255, 0, 0, 100 ) ) h.setWidth( 2 ) return h def finished(): self.timer.stop() self.timer.timeout.disconnect( finished ) del self.flash self.flash = getFlash() self.timer.timeout.connect( finished ) self.timer.start( 500 ) # Milliseconds before finishing the flash def zoom(self, layer, geometry): def getBoudingBoxGeomCanvas(): crsLayer = layer.crs() crsCanvas = self.project.crs() if not crsLayer == crsCanvas: ct = QgsCoordinateTransform( layer.crs(), self.project.crs(), self.project ) bbox = ct.transform( geometry.boundingBox() ) else: bbox = geometry.boundingBox() return bbox self.canvas.setExtent( getBoudingBoxGeomCanvas() ) self.canvas.zoomByFactor( 1.05 ) self.canvas.refresh() self.highlight( layer, geometry ) def highlightFeature(self, layer, feature): if not feature.hasGeometry(): return geom = feature.geometry() self.highlight( layer, geom ) def zoomFeature(self, layer, feature): if not feature.hasGeometry(): return geom = feature.geometry() self.zoom( layer, geom )
def fetchFiles(self, urls): self.logT("TileLayer.fetchFiles() starts") # create a QEventLoop object that belongs to the current thread (if ver. > 2.1, it is render thread) eventLoop = QEventLoop() self.logT("Create event loop: " + str(eventLoop)) # DEBUG # QObject.connect(self, SIGNAL("allRepliesFinished()"), eventLoop.quit) self.allRepliesFinished.connect(eventLoop.quit) # create a timer to watch whether rendering is stopped watchTimer = QTimer() watchTimer.timeout.connect(eventLoop.quit) # send a fetch request to the main thread # self.emit(SIGNAL("fetchRequest(QStringList)"), urls) self.fetchRequest.emit(urls) # wait for the fetch to finish tick = 0 interval = 500 timeoutTick = self.downloadTimeout / interval watchTimer.start(interval) while tick < timeoutTick: # run event loop for 0.5 seconds at maximum eventLoop.exec_() if debug_mode: qDebug("watchTimerTick: %d" % tick) qDebug("unfinished downloads: %d" % self.downloader.unfinishedCount()) if self.downloader.unfinishedCount( ) == 0 or self.renderContext.renderingStopped(): break tick += 1 watchTimer.stop() if tick == timeoutTick and self.downloader.unfinishedCount() > 0: self.log("fetchFiles timeout") # self.emitShowBarMessage("fetchFiles timeout", duration=5) #DEBUG self.downloader.abort() self.downloader.errorStatus = Downloader.TIMEOUT_ERROR files = self.downloader.fetchedFiles watchTimer.timeout.disconnect(eventLoop.quit) # # QObject.disconnect(self, SIGNAL("allRepliesFinished()"), eventLoop.quit) self.allRepliesFinished.disconnect(eventLoop.quit) self.logT("TileLayer.fetchFiles() ends") return files
def fetchFiles(self, urls): self.logT("TileLayer.fetchFiles() starts") # create a QEventLoop object that belongs to the current thread (if ver. > 2.1, it is render thread) eventLoop = QEventLoop() self.logT("Create event loop: " + str(eventLoop)) # DEBUG # QObject.connect(self, SIGNAL("allRepliesFinished()"), eventLoop.quit) self.allRepliesFinished.connect(eventLoop.quit) # create a timer to watch whether rendering is stopped watchTimer = QTimer() watchTimer.timeout.connect(eventLoop.quit) # send a fetch request to the main thread # self.emit(SIGNAL("fetchRequest(QStringList)"), urls) self.fetchRequest.emit(urls) # wait for the fetch to finish tick = 0 interval = 500 timeoutTick = self.downloadTimeout / interval watchTimer.start(interval) while tick < timeoutTick: # run event loop for 0.5 seconds at maximum eventLoop.exec_() if debug_mode: qDebug("watchTimerTick: %d" % tick) qDebug("unfinished downloads: %d" % self.downloader.unfinishedCount()) if self.downloader.unfinishedCount() == 0 or self.renderContext.renderingStopped(): break tick += 1 watchTimer.stop() if tick == timeoutTick and self.downloader.unfinishedCount() > 0: self.log("fetchFiles timeout") # self.emitShowBarMessage("fetchFiles timeout", duration=5) #DEBUG self.downloader.abort() self.downloader.errorStatus = Downloader.TIMEOUT_ERROR files = self.downloader.fetchedFiles watchTimer.timeout.disconnect(eventLoop.quit) # # QObject.disconnect(self, SIGNAL("allRepliesFinished()"), eventLoop.quit) self.allRepliesFinished.disconnect(eventLoop.quit) self.logT("TileLayer.fetchFiles() ends") return files
class QvBafarada(QLabel): ''' Mostra un text simulant una bafarada (globus) de còmic en una posició relativa al widget pare (TOPLEFT, TOPRIGHT, BOTTOMLEFT o BOTTOMRIGHT) Si no s'especifica pare, es mostrarà on li sembli, així que seria convenient fer-li un move a la posició que es vulgui La bafarada es mostra fent-li un show, i s'oculta per si sola, sense necessitat de fer res. ''' TOPLEFT=0 TOPRIGHT=1 BOTTOMLEFT=2 BOTTOMRIGHT=3 def __init__(self,text,parent=None, pos=BOTTOMRIGHT, temps=5): super().__init__(text) self.parent=parent self.setFont(QvConstants.FONTTEXT) self.setWordWrap(True) self.pos=pos self.temps=temps self.setStyleSheet(''' background: %s; color: %s; padding: 2px; border: 2px solid %s; border-radius: 10px; margin: 0px; '''%(QvConstants.COLORBLANCHTML,QvConstants.COLORFOSCHTML, QvConstants.COLORDESTACATHTML)) self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) # self.setWindowFlags(Qt.Window | Qt.WA_TranslucentBackground | Qt.FramelessWindowHint) def paintEvent(self,event): super().paintEvent(event) def show(self): super().show() self.timer=QTimer(self) self.timer.setSingleShot(True) self.timer.timeout.connect(lambda: self.hide()) self.timer.start(self.temps*1000) # self.show() if self.parent is not None: #Ja que python no té switch-case, ho fem amb una llista pos=[(10,10),(self.parent.width()-self.width()-10,10),(10,self.parent.height()-self.height()-10),(self.parent.width()-self.width()-10,self.parent.height()-self.height()-10)][self.pos] self.move(*pos) def oculta(self): self.hide() self.timer.stop()
def _post_sync(self, qurl: QUrl, timeout: int = 20000, data: bytes = b'', content_type=None): ''' synchronous POST-request ''' request = QNetworkRequest(qurl) if content_type: request.setHeader(QNetworkRequest.ContentTypeHeader, content_type) # newer versions of QGIS (3.6+) support synchronous requests if hasattr(self._manager, 'blockingPost'): reply = self._manager.blockingPost(request, data, forceRefresh=True) # use blocking event loop for older versions else: loop = QEventLoop() timer = QTimer() timer.setSingleShot(True) # reply or timeout break event loop, whoever comes first timer.timeout.connect(loop.quit) reply = self._manager.post(request, data) reply.finished.connect(loop.quit) timer.start(timeout) # start blocking loop loop.exec() loop.deleteLater() if not timer.isActive(): reply.deleteLater() raise ConnectionError('Timeout') timer.stop() if reply.error(): self.error.emit(reply.errorString()) raise ConnectionError(reply.errorString()) res = Reply(reply) self.finished.emit(res) return res
def render(self): """ do the rendering. This function is called in the worker thread """ debug("[WORKER THREAD] Calling request() asynchronously", 3) QMetaObject.invokeMethod(self.controller, "request") # setup a timer that checks whether the rendering has not been stopped # in the meanwhile timer = QTimer() timer.setInterval(50) timer.timeout.connect(self.onTimeout) timer.start() debug("[WORKER THREAD] Waiting for the async request to complete", 3) self.loop = QEventLoop() self.controller.finished.connect(self.loop.exit) self.loop.exec_() debug("[WORKER THREAD] Async request finished", 3) painter = self.context.painter() painter.drawImage(0, 0, self.controller.img) return True
def render(self): """ do the rendering. This function is called in the worker thread """ debug("[WORKER THREAD] Calling request() asynchronously", 3) QMetaObject.invokeMethod(self.controller, "request") # setup a timer that checks whether the rendering has not been stopped # in the meanwhile timer = QTimer() timer.setInterval(50) timer.timeout.connect(self.onTimeout) timer.start() debug("[WORKER THREAD] Waiting for the async request to complete", 3) self.loop = QEventLoop() self.controller.finished.connect(self.loop.exit) self.loop.exec_() debug("[WORKER THREAD] Async request finished", 3) painter = self.context.painter() painter.drawImage(0, 0, self.controller.img) return True
def get_topping_file_model(self, id_list): topping_file_cache = IliToppingFileCache( self.import_schema_configuration.base_configuration, id_list) # we wait for the download or we timeout after 30 seconds and we apply what we have loop = QEventLoop() topping_file_cache.download_finished.connect(lambda: loop.quit()) timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(lambda: loop.quit()) timer.start(30000) topping_file_cache.refresh() self.log_panel.print_info(self.tr("- - Downloading…"), LogColor.COLOR_TOPPING) if len(topping_file_cache.downloaded_files) != len(id_list): loop.exec() if len(topping_file_cache.downloaded_files) == len(id_list): self.log_panel.print_info( self.tr("- - All topping files successfully downloaded"), LogColor.COLOR_TOPPING, ) else: missing_file_ids = id_list for downloaded_file_id in topping_file_cache.downloaded_files: if downloaded_file_id in missing_file_ids: missing_file_ids.remove(downloaded_file_id) self.log_panel.print_info( self. tr("- - Some topping files where not successfully downloaded: {}" ).format(" ".join(missing_file_ids)), LogColor.COLOR_TOPPING, ) return topping_file_cache.model
class Downloader(QObject): NOT_FOUND = 0 NO_ERROR = 0 TIMEOUT_ERROR = 4 UNKNOWN_ERROR = -1 replyFinished = pyqtSignal(str, int, int) def __init__(self, parent=None): QObject.__init__(self, parent) self.queue = [] self.redirected_urls = {} self.requestingUrls = [] self.replies = [] self.eventLoop = QEventLoop() self.sync = False self.fetchedFiles = {} self.clearCounts() self.timer = QTimer() self.timer.setSingleShot(True) self.timer.timeout.connect(self.fetchTimedOut) # network settings self.userAgent = "QuickMapServices tile layer (+https://github.com/nextgis/quickmapservices)" self.max_connection = 4 self.default_cache_expiration = 24 self.errorStatus = Downloader.NO_ERROR def clearCounts(self): self.fetchSuccesses = 0 self.fetchErrors = 0 self.cacheHits = 0 def fetchTimedOut(self): self.log("Downloader.timeOut()") self.abort() self.errorStatus = Downloader.TIMEOUT_ERROR def abort(self): # clear queue and abort sent requests self.queue = [] self.timer.stop() for reply in self.replies: reply.abort() self.errorStatus = Downloader.UNKNOWN_ERROR def replyFinishedSlot(self): reply = self.sender() url = reply.request().url().toString() self.log("replyFinishedSlot: %s" % url) if not url in self.fetchedFiles: self.fetchedFiles[url] = None self.requestingUrls.remove(url) self.replies.remove(reply) isFromCache = 0 httpStatusCode = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) if reply.error() == QNetworkReply.NoError: if httpStatusCode == 301: new_url = str(reply.rawHeader("Location")) self.addToQueue(new_url, url) else: self.fetchSuccesses += 1 if reply.attribute(QNetworkRequest.SourceIsFromCacheAttribute): self.cacheHits += 1 isFromCache = 1 elif not reply.hasRawHeader("Cache-Control"): cache = QgsNetworkAccessManager.instance().cache() if cache: metadata = cache.metaData(reply.request().url()) # self.log("Expiration date: " + metadata.expirationDate().toString().encode("utf-8")) if metadata.expirationDate().isNull(): metadata.setExpirationDate( QDateTime.currentDateTime().addSecs(self.default_cache_expiration * 60 * 60)) cache.updateMetaData(metadata) self.log( "Default expiration date has been set: %s (%d h)" % (url, self.default_cache_expiration)) if reply.isReadable(): data = reply.readAll() if self.redirected_urls.has_key(url): url = self.redirected_urls[url] self.fetchedFiles[url] = data else: qDebug("http status code: " + str(httpStatusCode)) # self.emit(SIGNAL('replyFinished(QString, int, int)'), url, reply.error(), isFromCache) self.replyFinished.emit(url, reply.error(), isFromCache) else: if self.sync and httpStatusCode == 404: self.fetchedFiles[url] = self.NOT_FOUND self.fetchErrors += 1 if self.errorStatus == self.NO_ERROR: self.errorStatus = self.UNKNOWN_ERROR reply.deleteLater() if debug_mode: qDebug("queue: %d, requesting: %d" % (len(self.queue), len(self.requestingUrls))) if len(self.queue) + len(self.requestingUrls) == 0: # all replies have been received if self.sync: self.logT("eventLoop.quit()") self.eventLoop.quit() else: self.timer.stop() elif len(self.queue) > 0: # start fetching the next file self.fetchNext() self.log("replyFinishedSlot End: %s" % url) def fetchNext(self): if len(self.queue) == 0: return url = self.queue.pop(0) self.log("fetchNext: %s" % url) request = QNetworkRequest(QUrl(url)) request.setRawHeader("User-Agent", self.userAgent) reply = QgsNetworkAccessManager.instance().get(request) reply.finished.connect(self.replyFinishedSlot) self.requestingUrls.append(url) self.replies.append(reply) return reply def fetchFiles(self, urlList, timeout_ms=0): self.log("fetchFiles()") self.sync = True self.queue = [] self.redirected_urls = {} self.clearCounts() self.errorStatus = Downloader.NO_ERROR self.fetchedFiles = {} if len(urlList) == 0: return self.fetchedFiles for url in urlList: self.addToQueue(url) for i in range(self.max_connection): self.fetchNext() if timeout_ms > 0: self.timer.setInterval(timeout_ms) self.timer.start() self.logT("eventLoop.exec_(): " + str(self.eventLoop)) self.eventLoop.exec_() self.log("fetchFiles() End: %d" % self.errorStatus) if timeout_ms > 0: self.timer.stop() return self.fetchedFiles def addToQueue(self, url, redirected_from=None): if url in self.queue: return False self.queue.append(url) if redirected_from is not None: self.redirected_urls[url] = redirected_from return True def queueCount(self): return len(self.queue) def finishedCount(self): return len(self.fetchedFiles) def unfinishedCount(self): return len(self.queue) + len(self.requestingUrls) def log(self, msg): if debug_mode: qDebug(msg) def logT(self, msg): if debug_mode: qDebug("%s: %s" % (str(threading.current_thread()), msg)) def fetchFilesAsync(self, urlList, timeout_ms=0): self.log("fetchFilesAsync()") self.sync = False self.queue = [] self.clearCounts() self.errorStatus = Downloader.NO_ERROR self.fetchedFiles = {} if len(urlList) == 0: return self.fetchedFiles for url in urlList: self.addToQueue(url) for i in range(self.max_connection): self.fetchNext() if timeout_ms > 0: self.timer.setInterval(timeout_ms) self.timer.start()
class ApiDimensioning(ApiParent): def __init__(self, iface, settings, controller, plugin_dir): """ Class constructor """ ApiParent.__init__(self, iface, settings, controller, plugin_dir) self.iface = iface self.settings = settings self.controller = controller self.plugin_dir = plugin_dir self.canvas = self.iface.mapCanvas() self.emit_point = QgsMapToolEmitPoint(self.canvas) self.canvas.setMapTool(self.emit_point) # Snapper self.snapper_manager = SnappingConfigManager(self.iface) self.snapper = self.snapper_manager.get_snapper() def open_form(self, new_feature=None, layer=None, new_feature_id=None): self.dlg_dim = ApiDimensioningUi() self.load_settings(self.dlg_dim) # Set signals actionSnapping = self.dlg_dim.findChild(QAction, "actionSnapping") actionSnapping.triggered.connect(partial(self.snapping, actionSnapping)) self.set_icon(actionSnapping, "103") actionOrientation = self.dlg_dim.findChild(QAction, "actionOrientation") actionOrientation.triggered.connect(partial(self.orientation, actionOrientation)) self.set_icon(actionOrientation, "133") self.dlg_dim.btn_accept.clicked.connect(partial(self.save_dimensioning, new_feature, layer)) self.dlg_dim.btn_cancel.clicked.connect(partial(self.cancel_dimensioning)) self.dlg_dim.dlg_closed.connect(partial(self.cancel_dimensioning)) self.dlg_dim.dlg_closed.connect(partial(self.save_settings, self.dlg_dim)) # Set layers dimensions, node and connec self.layer_dimensions = self.controller.get_layer_by_tablename("v_edit_dimensions") self.layer_node = self.controller.get_layer_by_tablename("v_edit_node") self.layer_connec = self.controller.get_layer_by_tablename("v_edit_connec") self.create_map_tips() body = self.create_body() # Get layers under mouse clicked sql = f"SELECT gw_api_getdimensioning($${{{body}}}$$)::text" row = self.controller.get_row(sql, log_sql=True, commit=True) if row is None or row[0] is None: self.controller.show_message("NOT ROW FOR: " + sql, 2) return False # Parse string to order dict into List complet_result = [json.loads(row[0], object_pairs_hook=OrderedDict)] layout_list = [] for field in complet_result[0]['body']['data']['fields']: label, widget = self.set_widgets(self.dlg_dim, complet_result, field) if widget.objectName() == 'id': utils_giswater.setWidgetText(self.dlg_dim, widget, new_feature_id) layout = self.dlg_dim.findChild(QGridLayout, field['layoutname']) # Take the QGridLayout with the intention of adding a QSpacerItem later if layout not in layout_list and layout.objectName() not in ('top_layout', 'bot_layout_1', 'bot_layout_2'): layout_list.append(layout) # Add widgets into layout if field['layoutname'] in ('top_layout', 'bot_layout_1', 'bot_layout_2'): layout.addWidget(label, 0, field['layout_order']) layout.addWidget(widget, 1, field['layout_order']) else: self.put_widgets(self.dlg_dim, field, label, widget) # Add a QSpacerItem into each QGridLayout of the list for layout in layout_list: vertical_spacer1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) layout.addItem(vertical_spacer1) self.open_dialog(self.dlg_dim) return False, False def cancel_dimensioning(self): self.iface.actionRollbackEdits().trigger() self.close_dialog(self.dlg_dim) def save_dimensioning(self, new_feature, layer): # Insert new feature into db layer.updateFeature(new_feature) layer.commitChanges() # Create body fields = '' list_widgets = self.dlg_dim.findChildren(QLineEdit) for widget in list_widgets: widget_name = widget.objectName() widget_value = utils_giswater.getWidgetText(self.dlg_dim, widget) if widget_value == 'null': continue fields += f'"{widget_name}":"{widget_value}",' srid = self.controller.plugin_settings_value('srid') sql = f"SELECT ST_GeomFromText('{new_feature.geometry().asWkt()}', {srid})" the_geom = self.controller.get_row(sql, commit=True, log_sql=True) fields += f'"the_geom":"{the_geom[0]}"' feature = '"tableName":"v_edit_dimensions"' body = self.create_body(feature=feature, filter_fields=fields) # Execute query sql = f"SELECT gw_api_setdimensioning($${{{body}}}$$)::text" row = self.controller.get_row(sql, log_sql=True, commit=True) # Close dialog self.close_dialog(self.dlg_dim) def deactivate_signals(self, action): self.snapper_manager.remove_marker() try: self.canvas.xyCoordinates.disconnect() except TypeError as e: pass try: self.emit_point.canvasClicked.disconnect() except TypeError as e: pass if not action.isChecked(): action.setChecked(False) return True return False def snapping(self, action): # Set active layer and set signals if self.deactivate_signals(action): return self.dlg_dim.actionOrientation.setChecked(False) self.iface.setActiveLayer(self.layer_node) self.canvas.xyCoordinates.connect(self.mouse_move) self.emit_point.canvasClicked.connect(partial(self.click_button_snapping, action)) def mouse_move(self, point): # Hide marker and get coordinates self.snapper_manager.remove_marker() event_point = self.snapper_manager.get_event_point(point=point) # Snapping result = self.snapper_manager.snap_to_background_layers(event_point) if self.snapper_manager.result_is_valid(): layer = self.snapper_manager.get_snapped_layer(result) # Check feature if layer == self.layer_node or layer == self.layer_connec: self.snapper_manager.add_marker(result) def click_button_snapping(self, action, point, btn): if not self.layer_dimensions: return if btn == Qt.RightButton: if btn == Qt.RightButton: action.setChecked(False) self.deactivate_signals(action) return layer = self.layer_dimensions self.iface.setActiveLayer(layer) layer.startEditing() # Get coordinates event_point = self.snapper_manager.get_event_point(point=point) # Snapping result = self.snapper_manager.snap_to_background_layers(event_point) if self.snapper_manager.result_is_valid(): layer = self.snapper_manager.get_snapped_layer(result) # Check feature if layer == self.layer_node: feat_type = 'node' elif layer == self.layer_connec: feat_type = 'connec' else: return # Get the point snapped_feat = self.snapper_manager.get_snapped_feature(result) feature_id = self.snapper_manager.get_snapped_feature_id(result) element_id = snapped_feat.attribute(feat_type + '_id') # Leave selection layer.select([feature_id]) # Get depth of the feature self.project_type = self.controller.get_project_type() if self.project_type == 'ws': fieldname = "depth" elif self.project_type == 'ud' and feat_type == 'node': fieldname = "ymax" elif self.project_type == 'ud' and feat_type == 'connec': fieldname = "connec_depth" sql = (f"SELECT {fieldname} " f"FROM {feat_type} " f"WHERE {feat_type}_id = '{element_id}'") row = self.controller.get_row(sql) if not row: return utils_giswater.setText(self.dlg_dim, "depth", row[0]) utils_giswater.setText(self.dlg_dim, "feature_id", element_id) utils_giswater.setText(self.dlg_dim, "feature_type", feat_type.upper()) def orientation(self, action): if self.deactivate_signals(action): return self.dlg_dim.actionSnapping.setChecked(False) self.emit_point.canvasClicked.connect(partial(self.click_button_orientation, action)) def click_button_orientation(self, action, point, btn): if not self.layer_dimensions: return if btn == Qt.RightButton: action.setChecked(False) self.deactivate_signals(action) return self.x_symbol = self.dlg_dim.findChild(QLineEdit, "x_symbol") self.x_symbol.setText(str(int(point.x()))) self.y_symbol = self.dlg_dim.findChild(QLineEdit, "y_symbol") self.y_symbol.setText(str(int(point.y()))) def create_map_tips(self): """ Create MapTips on the map """ row = self.controller.get_config('dim_tooltip') if not row or row[0].lower() != 'true': return self.timer_map_tips = QTimer(self.canvas) self.map_tip_node = QgsMapTip() self.map_tip_connec = QgsMapTip() self.canvas.xyCoordinates.connect(self.map_tip_changed) self.timer_map_tips.timeout.connect(self.show_map_tip) self.timer_map_tips_clear = QTimer(self.canvas) self.timer_map_tips_clear.timeout.connect(self.clear_map_tip) def map_tip_changed(self, point): """ SLOT. Initialize the Timer to show MapTips on the map """ if self.canvas.underMouse(): self.last_map_position = QgsPointXY(point.x(), point.y()) self.map_tip_node.clear(self.canvas) self.map_tip_connec.clear(self.canvas) self.timer_map_tips.start(100) def show_map_tip(self): """ Show MapTips on the map """ self.timer_map_tips.stop() if self.canvas.underMouse(): point_qgs = self.last_map_position point_qt = self.canvas.mouseLastXY() if self.layer_node: self.map_tip_node.showMapTip(self.layer_node, point_qgs, point_qt, self.canvas) if self.layer_connec: self.map_tip_connec.showMapTip(self.layer_connec, point_qgs, point_qt, self.canvas) self.timer_map_tips_clear.start(1000) def clear_map_tip(self): """ Clear MapTips """ self.timer_map_tips_clear.stop() self.map_tip_node.clear(self.canvas) self.map_tip_connec.clear(self.canvas) def set_widgets(self, dialog, complet_result, field): widget = None label = None if field['label']: label = QLabel() label.setObjectName('lbl_' + field['widgetname']) label.setText(field['label'].capitalize()) if field['stylesheet'] is not None and 'label' in field['stylesheet']: label = self.set_setStyleSheet(field, label) if 'tooltip' in field: label.setToolTip(field['tooltip']) else: label.setToolTip(field['label'].capitalize()) if field['widgettype'] == 'text' or field['widgettype'] == 'typeahead': completer = QCompleter() widget = self.add_lineedit(field) widget = self.set_widget_size(widget, field) widget = self.set_data_type(field, widget) if field['widgettype'] == 'typeahead': widget = self.manage_lineedit(field, dialog, widget, completer) # if widget.property('column_id') == self.field_id: # self.feature_id = widget.text() # # Get selected feature # self.feature = self.get_feature_by_id(self.layer, self.feature_id, self.field_id) elif field['widgettype'] == 'combo': widget = self.add_combobox(field) widget = self.set_widget_size(widget, field) elif field['widgettype'] == 'check': widget = self.add_checkbox(field) elif field['widgettype'] == 'datepickertime': widget = self.add_calendar(dialog, field) elif field['widgettype'] == 'button': widget = self.add_button(dialog, field) widget = self.set_widget_size(widget, field) elif field['widgettype'] == 'hyperlink': widget = self.add_hyperlink(dialog, field) widget = self.set_widget_size(widget, field) elif field['widgettype'] == 'hspacer': widget = self.add_horizontal_spacer() elif field['widgettype'] == 'vspacer': widget = self.add_verical_spacer() elif field['widgettype'] == 'textarea': widget = self.add_textarea(field) elif field['widgettype'] in ('spinbox', 'doubleSpinbox'): widget = self.add_spinbox(field) elif field['widgettype'] == 'tableView': widget = self.add_tableview(complet_result, field) widget = self.set_headers(widget, field) widget = self.populate_table(widget, field) widget = self.set_columns_config(widget, field['widgetname'], sort_order=1, isQStandardItemModel=True) utils_giswater.set_qtv_config(widget) return label, widget
class OpenLayersOverviewWidget(QWidget, Ui_Form): def __init__(self, iface, dockwidget, olLayerTypeRegistry): QWidget.__init__(self) Ui_Form.__init__(self) self.setupUi(self) self.__canvas = iface.mapCanvas() self.__dockwidget = dockwidget self.__olLayerTypeRegistry = olLayerTypeRegistry self.__initLayerOL = False self.__fileNameImg = '' self.__srsOL = QgsCoordinateReferenceSystem( 3857, QgsCoordinateReferenceSystem.EpsgCrsId) self.__marker = MarkerCursor(self.__canvas, self.__srsOL) self.__manager = None # Need persist for PROXY bindogr.initOgr() self.__init() def __init(self): self.checkBoxHideCross.setEnabled(False) self.__populateTypeMapGUI() self.__populateButtonBox() self.__registerObjJS() self.lbStatusRead.setVisible(False) self.__setConnections() self.__timerMapReady = QTimer() self.__timerMapReady.setSingleShot(True) self.__timerMapReady.setInterval(20) self.__timerMapReady.timeout.connect(self.__checkMapReady) def __del__(self): self.__marker.reset() # Disconnect Canvas # Canvas QgsMapCanvas.extentsChanged.disconnect(self.__canvas) # Doc WidgetparentWidget QDockWidget.visibilityChanged.disconnect(self.__dockwidget) def __populateButtonBox(self): pathPlugin = "%s%s%%s" % (os.path.dirname(__file__), os.path.sep) self.pbRefresh.setIcon(QIcon(pathPlugin % "mActionDraw.png")) self.pbRefresh.setEnabled(False) self.pbAddRaster.setIcon(QIcon(pathPlugin % "mActionAddRasterLayer.png")) self.pbAddRaster.setEnabled(False) self.pbCopyKml.setIcon(QIcon(pathPlugin % "kml.png")) self.pbCopyKml.setEnabled(False) self.pbSaveImg.setIcon(QIcon(pathPlugin % "mActionSaveMapAsImage.png")) self.pbSaveImg.setEnabled(False) def __populateTypeMapGUI(self): pathPlugin = "%s%s%%s" % (os.path.dirname(__file__), os.path.sep) totalLayers = len(self.__olLayerTypeRegistry.types()) for id in range(totalLayers): layer = self.__olLayerTypeRegistry.getById(id) name = str(layer.displayName) icon = QIcon(pathPlugin % layer.groupIcon) self.comboBoxTypeMap.addItem(icon, name, id) def __setConnections(self): # Check Box self.checkBoxEnableMap.stateChanged.connect( self.__signal_checkBoxEnableMap_stateChanged) self.checkBoxHideCross.stateChanged.connect( self.__signal_checkBoxHideCross_stateChanged) # comboBoxTypeMap self.comboBoxTypeMap.currentIndexChanged.connect( self.__signal_comboBoxTypeMap_currentIndexChanged) # Canvas self.__canvas.extentsChanged.connect( self.__signal_canvas_extentsChanged) # Doc WidgetparentWidget self.__dockwidget.visibilityChanged.connect( self.__signal_DocWidget_visibilityChanged) # WebView Map self.webViewMap.page().mainFrame().javaScriptWindowObjectCleared.connect( self.__registerObjJS) # Push Button self.pbRefresh.clicked.connect( self.__signal_pbRefresh_clicked) self.pbAddRaster.clicked.connect( self.__signal_pbAddRaster_clicked) self.pbCopyKml.clicked.connect( self.__signal_pbCopyKml_clicked) self.pbSaveImg.clicked.connect( self.__signal_pbSaveImg_clicked) def __registerObjJS(self): self.webViewMap.page().mainFrame().addToJavaScriptWindowObject( "MarkerCursorQGis", self.__marker) def __signal_checkBoxEnableMap_stateChanged(self, state): enable = False if state == Qt.Unchecked: self.__marker.reset() else: if self.__canvas.layerCount() == 0: QMessageBox.warning(self, QApplication.translate( "OpenLayersOverviewWidget", "OpenLayers Overview"), QApplication.translate( "OpenLayersOverviewWidget", "At least one layer in map canvas required")) self.checkBoxEnableMap.setCheckState(Qt.Unchecked) else: enable = True if not self.__initLayerOL: self.__initLayerOL = True self.__setWebViewMap(0) else: self.__refreshMapOL() # GUI if enable: self.lbStatusRead.setVisible(False) self.webViewMap.setVisible(True) else: self.lbStatusRead.setText("") self.lbStatusRead.setVisible(True) self.webViewMap.setVisible(False) self.webViewMap.setEnabled(enable) self.comboBoxTypeMap.setEnabled(enable) self.pbRefresh.setEnabled(enable) self.pbAddRaster.setEnabled(enable) self.pbCopyKml.setEnabled(enable) self.pbSaveImg.setEnabled(enable) self.checkBoxHideCross.setEnabled(enable) def __signal_checkBoxHideCross_stateChanged(self, state): if state == Qt.Checked: self.__marker.reset() self.__marker.setVisible(False) else: self.__marker.setVisible(True) self.__refreshMapOL() def __signal_DocWidget_visibilityChanged(self, visible): if self.__canvas.layerCount() == 0: return self.checkBoxEnableMap.setCheckState(Qt.Unchecked) self.__signal_checkBoxEnableMap_stateChanged(Qt.Unchecked) def __signal_comboBoxTypeMap_currentIndexChanged(self, index): self.__setWebViewMap(index) def __signal_canvas_extentsChanged(self): if self.__canvas.layerCount() == 0 or not self.webViewMap.isVisible(): return if self.checkBoxEnableMap.checkState() == Qt.Checked: self.__refreshMapOL() def __signal_pbRefresh_clicked(self, checked): index = self.comboBoxTypeMap.currentIndex() self.__setWebViewMap(index) def __signal_pbAddRaster_clicked(self, checked): index = self.comboBoxTypeMap.currentIndex() layer = self.__olLayerTypeRegistry.getById(index) QGuiApplication.setOverrideCursor(Qt.WaitCursor) layer.addLayer() QGuiApplication.restoreOverrideCursor() def __signal_pbCopyKml_clicked(self, cheked): # Extent Openlayers action = "map.getExtent().toGeometry().toString();" wkt = self.webViewMap.page().mainFrame().evaluateJavaScript(action) rect = QgsGeometry.fromWkt(wkt).boundingBox() srsGE = QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem.EpsgCrsId) coodTrans = QgsCoordinateTransform(self.__srsOL, srsGE, QgsProject.instance()) rect = coodTrans.transform( rect, QgsCoordinateTransform.ForwardTransform) line = QgsGeometry.fromRect(rect).asPolygon()[0] wkt = str(QgsGeometry.fromPolylineXY(line).asWkt()) # Kml proj4 = str(srsGE.toProj4()) kmlLine = bindogr.exportKml(wkt, proj4) kml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"\ "<kml xmlns=\"http://www.opengis.net/kml/2.2\" " \ "xmlns:gx=\"http://www.google.com/kml/ext/2.2\" " \ "xmlns:kml=\"http://www.opengis.net/kml/2.2\" " \ "xmlns:atom=\"http://www.w3.org/2005/Atom\">" \ "<Placemark>" \ "<name>KML from Plugin Openlayers Overview for QGIS</name>" \ "<description>Extent of openlayers map from Plugin Openlayers \ Overview for QGIS</description>"\ "%s" \ "</Placemark></kml>" % kmlLine clipBoard = QApplication.clipboard() clipBoard.setText(kml) def __signal_pbSaveImg_clicked(self, cheked): if type(self.__fileNameImg) == tuple: self.__fileNameImg = self.__fileNameImg[0] fileName = QFileDialog.getSaveFileName(self, QApplication.translate( "OpenLayersOverviewWidget", "Save image"), self.__fileNameImg, QApplication.translate( "OpenLayersOverviewWidget", "Image(*.jpg)")) if not fileName == '': self.__fileNameImg = fileName else: return img = QImage(self.webViewMap.page().mainFrame().contentsSize(), QImage.Format_ARGB32_Premultiplied) imgPainter = QPainter() imgPainter.begin(img) self.webViewMap.page().mainFrame().render(imgPainter) imgPainter.end() img.save(fileName[0], "JPEG") def __signal_webViewMap_loadFinished(self, ok): if ok is False: QMessageBox.warning(self, QApplication.translate( "OpenLayersOverviewWidget", "OpenLayers Overview"), QApplication.translate( "OpenLayersOverviewWidget", "Error loading page!")) else: # wait until OpenLayers map is ready self.__checkMapReady() self.lbStatusRead.setVisible(False) self.webViewMap.setVisible(True) self.webViewMap.page().mainFrame().loadFinished.disconnect( self.__signal_webViewMap_loadFinished) def __setWebViewMap(self, id): layer = self.__olLayerTypeRegistry.getById(id) self.lbStatusRead.setText("Loading " + layer.displayName + " ...") self.lbStatusRead.setVisible(True) self.webViewMap.setVisible(False) self.webViewMap.page().mainFrame().loadFinished.connect( self.__signal_webViewMap_loadFinished) url = layer.html_url() self.webViewMap.page().mainFrame().load(QUrl(url)) def __checkMapReady(self): if self.webViewMap.page().mainFrame().evaluateJavaScript( "map != undefined"): # map ready self.__refreshMapOL() else: # wait for map self.__timerMapReady.start() def __refreshMapOL(self): # catch Exception where lat/long exceed limit of the loaded layer # the Exception name is unknown latlon = None try: latlon = self.__getCenterLongLat2OL() except Exception as e: QgsLogger().warning(e.args[0]) if latlon: action = "map.setCenter(new OpenLayers.LonLat(%f, %f));" % (latlon) self.webViewMap.page().mainFrame().evaluateJavaScript(action) action = "map.zoomToScale(%f);" % self.__canvas.scale() self.webViewMap.page().mainFrame().evaluateJavaScript(action) self.webViewMap.page().mainFrame().evaluateJavaScript( "oloMarker.changeMarker();") def __getCenterLongLat2OL(self): pntCenter = self.__canvas.extent().center() crsCanvas = self.__canvas.mapSettings().destinationCrs() if crsCanvas != self.__srsOL: coodTrans = QgsCoordinateTransform(crsCanvas, self.__srsOL, QgsProject.instance()) pntCenter = coodTrans.transform( pntCenter, QgsCoordinateTransform.ForwardTransform) return tuple([pntCenter.x(), pntCenter.y()])
class OpenlayersController(QObject): """ Helper class that deals with QWebPage. The object lives in GUI thread, its request() slot is asynchronously called from worker thread. See https://github.com/wonder-sk/qgis-mtr-example-plugin for basic example 1. Load Web Page with OpenLayers map 2. Update OL map extend according to QGIS canvas extent """ # signal that reports to the worker thread that the image is ready finished = pyqtSignal() def __init__(self, parent, context, webPage, layerType): QObject.__init__(self, parent) debug("OpenlayersController.__init__", 3) self.context = context self.layerType = layerType self.img = QImage() self.page = webPage self.page.loadFinished.connect(self.pageLoaded) # initial size for map self.page.setViewportSize(QSize(1, 1)) self.timerMapReady = QTimer() self.timerMapReady.setSingleShot(True) self.timerMapReady.setInterval(20) self.timerMapReady.timeout.connect(self.checkMapReady) self.timer = QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self.checkMapUpdate) self.timerMax = QTimer() self.timerMax.setSingleShot(True) # TODO: different timeouts for map types self.timerMax.setInterval(2500) self.timerMax.timeout.connect(self.mapTimeout) @pyqtSlot() def request(self): debug("[GUI THREAD] Processing request", 3) self.cancelled = False if not self.page.loaded: self.init_page() else: self.setup_map() def init_page(self): url = self.layerType.html_url() debug("page file: %s" % url) self.page.mainFrame().load(QUrl(url)) # wait for page to finish loading debug("OpenlayersWorker waiting for page to load", 3) def pageLoaded(self): debug("[GUI THREAD] pageLoaded", 3) if self.cancelled: self.emitErrorImage() return # wait until OpenLayers map is ready self.checkMapReady() def checkMapReady(self): debug("[GUI THREAD] checkMapReady", 3) if self.page.mainFrame().evaluateJavaScript("map != undefined"): # map ready self.page.loaded = True self.setup_map() else: # wait for map self.timerMapReady.start() def setup_map(self): rendererContext = self.context # FIXME: self.mapSettings.outputDpi() self.outputDpi = rendererContext.painter().device().logicalDpiX() debug(" extent: %s" % rendererContext.extent().toString(), 3) debug( " center: %lf, %lf" % (rendererContext.extent().center().x(), rendererContext.extent().center().y()), 3) debug( " size: %d, %d" % (rendererContext.painter().viewport().size().width(), rendererContext.painter().viewport().size().height()), 3) debug( " logicalDpiX: %d" % rendererContext.painter().device().logicalDpiX(), 3) debug(" outputDpi: %lf" % self.outputDpi) debug( " mapUnitsPerPixel: %f" % rendererContext.mapToPixel().mapUnitsPerPixel(), 3) # debug(" rasterScaleFactor: %s" % str(rendererContext. # rasterScaleFactor()), 3) # debug(" outputSize: %d, %d" % (self.iface.mapCanvas().mapRenderer(). # outputSize().width(), # self.iface.mapCanvas().mapRenderer(). # outputSize().height()), 3) # debug(" scale: %lf" % self.iface.mapCanvas().mapRenderer().scale(), # 3) # # if (self.page.lastExtent == rendererContext.extent() # and self.page.lastViewPortSize == rendererContext.painter(). # viewport().size() # and self.page.lastLogicalDpi == rendererContext.painter(). # device().logicalDpiX() # and self.page.lastOutputDpi == self.outputDpi # and self.page.lastMapUnitsPerPixel == rendererContext. # mapToPixel().mapUnitsPerPixel()): # self.renderMap() # self.finished.emit() # return self.targetSize = rendererContext.painter().viewport().size() if rendererContext.painter().device().logicalDpiX() != int( self.outputDpi): # use screen dpi for printing sizeFact = self.outputDpi / 25.4 / rendererContext.mapToPixel( ).mapUnitsPerPixel() self.targetSize.setWidth(rendererContext.extent().width() * sizeFact) self.targetSize.setHeight(rendererContext.extent().height() * sizeFact) debug( " targetSize: %d, %d" % (self.targetSize.width(), self.targetSize.height()), 3) # find matching OL resolution qgisRes = rendererContext.extent().width() / self.targetSize.width() olRes = None for res in self.page.resolutions(): if qgisRes >= res: olRes = res break if olRes is None: debug("No matching OL resolution found (QGIS resolution: %f)" % qgisRes) self.emitErrorImage() return # adjust OpenLayers viewport to match QGIS extent olWidth = rendererContext.extent().width() / olRes olHeight = rendererContext.extent().height() / olRes debug( " adjust viewport: %f -> %f: %f x %f" % (qgisRes, olRes, olWidth, olHeight), 3) olSize = QSize(int(olWidth), int(olHeight)) self.page.setViewportSize(olSize) self.page.mainFrame().evaluateJavaScript("map.updateSize();") self.img = QImage(olSize, QImage.Format_ARGB32_Premultiplied) self.page.extent = rendererContext.extent() debug( "map.zoomToExtent (%f, %f, %f, %f)" % (self.page.extent.xMinimum(), self.page.extent.yMinimum(), self.page.extent.xMaximum(), self.page.extent.yMaximum()), 3) self.page.mainFrame().evaluateJavaScript( "map.zoomToExtent(new OpenLayers.Bounds(%f, %f, %f, %f), true);" % (self.page.extent.xMinimum(), self.page.extent.yMinimum(), self.page.extent.xMaximum(), self.page.extent.yMaximum())) olextent = self.page.mainFrame().evaluateJavaScript("map.getExtent();") debug("Resulting OL extent: %s" % olextent, 3) if olextent is None or not hasattr(olextent, '__getitem__'): debug("map.zoomToExtent failed") # map.setCenter and other operations throw "undefined[0]: # TypeError: 'null' is not an object" on first page load # We ignore that and render the initial map with wrong extents # self.emitErrorImage() # return else: reloffset = abs((self.page.extent.yMaximum() - olextent["top"]) / self.page.extent.xMinimum()) debug("relative offset yMaximum %f" % reloffset, 3) if reloffset > 0.1: debug("OL extent shift failure: %s" % reloffset) self.emitErrorImage() return self.mapFinished = False self.timer.start() self.timerMax.start() def checkMapUpdate(self): if self.layerType.emitsLoadEnd: # wait for OpenLayers to finish loading loadEndOL = self.page.mainFrame().evaluateJavaScript("loadEnd") debug( "waiting for loadEnd: renderingStopped=%r loadEndOL=%r" % (self.context.renderingStopped(), loadEndOL), 4) if loadEndOL is not None: self.mapFinished = loadEndOL else: debug("OpenlayersLayer Warning: Could not get loadEnd") if self.mapFinished: self.timerMax.stop() self.timer.stop() self.renderMap() self.finished.emit() def renderMap(self): rendererContext = self.context if rendererContext.painter().device().logicalDpiX() != int( self.outputDpi): printScale = 25.4 / self.outputDpi # OL DPI to printer pixels rendererContext.painter().scale(printScale, printScale) # render OpenLayers to image painter = QPainter(self.img) self.page.mainFrame().render(painter) painter.end() if self.img.size() != self.targetSize: targetWidth = self.targetSize.width() targetHeight = self.targetSize.height() # scale using QImage for better quality debug( " scale image: %i x %i -> %i x %i" % (self.img.width(), self.img.height(), targetWidth, targetHeight), 3) self.img = self.img.scaled(targetWidth, targetHeight, Qt.KeepAspectRatio, Qt.SmoothTransformation) # save current state self.page.lastExtent = rendererContext.extent() self.page.lastViewPortSize = rendererContext.painter().viewport().size( ) self.page.lastLogicalDpi = rendererContext.painter().device( ).logicalDpiX() self.page.lastOutputDpi = self.outputDpi self.page.lastMapUnitsPerPixel = rendererContext.mapToPixel( ).mapUnitsPerPixel() def mapTimeout(self): debug("mapTimeout reached") self.timer.stop() # if not self.layerType.emitsLoadEnd: self.renderMap() self.finished.emit() def emitErrorImage(self): self.img = QImage() self.targetSize = self.img.size self.finished.emit()
class TimeManagerGuiControl(QObject): """This class controls all plugin-related GUI elements. Emitted signals are defined here.""" showOptions = pyqtSignal() signalExportVideo = pyqtSignal(str, int, bool, bool, bool) toggleTime = pyqtSignal() toggleArchaeology = pyqtSignal() back = pyqtSignal() forward = pyqtSignal() play = pyqtSignal() signalCurrentTimeUpdated = pyqtSignal(QDateTime) signalSliderTimeChanged = pyqtSignal(float) signalTimeFrameType = pyqtSignal(str) signalTimeFrameSize = pyqtSignal(int) signalSaveOptions = pyqtSignal() signalArchDigitsSpecified = pyqtSignal(int) signalArchCancelled = pyqtSignal() def __init__(self, iface, model): """Initialize the GUI control""" QObject.__init__(self) self.iface = iface self.model = model self.optionsDialog = None self.path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ui') # load the form self.dock = uic.loadUi(os.path.join(self.path, DOCK_WIDGET_FILE)) self.iface.addDockWidget(Qt.BottomDockWidgetArea, self.dock) self.dock.pushButtonExportVideo.setEnabled(False) # only enabled if there are managed layers self.dock.pushButtonOptions.clicked.connect(self.optionsClicked) self.dock.pushButtonExportVideo.clicked.connect(self.exportVideoClicked) self.dock.pushButtonToggleTime.clicked.connect(self.toggleTimeClicked) self.dock.pushButtonArchaeology.clicked.connect(self.archaeologyClicked) self.dock.pushButtonBack.clicked.connect(self.backClicked) self.dock.pushButtonForward.clicked.connect(self.forwardClicked) self.dock.pushButtonPlay.clicked.connect(self.playClicked) self.dock.dateTimeEditCurrentTime.dateTimeChanged.connect(self.currentTimeChangedDateText) # self.dock.horizontalTimeSlider.valueChanged.connect(self.currentTimeChangedSlider) self.sliderTimer = QTimer(self) self.sliderTimer.setInterval(250) self.sliderTimer.setSingleShot(True) self.sliderTimer.timeout.connect(self.currentTimeChangedSlider) self.dock.horizontalTimeSlider.valueChanged.connect(self.startTimer) self.dock.comboBoxTimeExtent.currentIndexChanged[str].connect(self.currentTimeFrameTypeChanged) self.dock.spinBoxTimeExtent.valueChanged.connect(self.currentTimeFrameSizeChanged) # this signal is responsible for rendering the label self.iface.mapCanvas().renderComplete.connect(self.renderLabel) # create shortcuts self.focusSC = QShortcut(QKeySequence("Ctrl+Space"), self.dock) self.focusSC.activated.connect(self.dock.horizontalTimeSlider.setFocus) # put default values self.dock.horizontalTimeSlider.setMinimum(conf.MIN_TIMESLIDER_DEFAULT) self.dock.horizontalTimeSlider.setMaximum(conf.MAX_TIMESLIDER_DEFAULT) self.dock.dateTimeEditCurrentTime.setMinimumDate(MIN_QDATE) self.showLabel = conf.DEFAULT_SHOW_LABEL self.exportEmpty = conf.DEFAULT_EXPORT_EMPTY self.labelOptions = TimestampLabelConfig(self.model) # placeholders for widgets that are added dynamically self.bcdateSpinBox = None # add to plugins toolbar try: self.action = QAction(QCoreApplication.translate("TimeManagerGuiControl", "Toggle visibility"), self.iface.mainWindow()) self.action.triggered.connect(self.toggleDock) self.iface.addPluginToMenu(QCoreApplication.translate("TimeManagerGuiControl", "&TimeManager"), self.action) except Exception as e: pass # OK for testing def startTimer(self): self.sliderTimer.start() def getLabelFormat(self): return self.labelOptions.fmt def getLabelFont(self): return self.labelOptions.font def getLabelSize(self): return self.labelOptions.size def getLabelColor(self): return self.labelOptions.color def getLabelBgColor(self): return self.labelOptions.bgcolor def getLabelPlacement(self): return self.labelOptions.placement def setLabelFormat(self, fmt): if not fmt: return self.labelOptions.fmt = fmt def setLabelFont(self, font): if not font: return self.labelOptions.font = font def setLabelSize(self, size): if not size: return self.labelOptions.size = size def setLabelColor(self, color): if not color: return self.labelOptions.color = color def setLabelBgColor(self, bgcolor): if not bgcolor: return self.labelOptions.bgcolor = bgcolor def setLabelPlacement(self, placement): if not placement: return self.labelOptions.placement = placement def toggleDock(self): self.dock.setVisible(not self.dock.isVisible()) def getOptionsDialog(self): return self.optionsDialog def showAnimationOptions(self): self.animationDialog = uic.loadUi(os.path.join(self.path, ANIMATION_WIDGET_FILE)) def selectFile(): self.animationDialog.lineEdit.setText(QFileDialog.getOpenFileName()) self.animationDialog.pushButton.clicked.connect(self.selectAnimationFolder) self.animationDialog.buttonBox.accepted.connect(self.sendAnimationOptions) self.animationDialog.show() def selectAnimationFolder(self): prev_directory = TimeManagerProjectHandler.plugin_setting(conf.LAST_ANIMATION_PATH_TAG) if prev_directory: self.animationDialog.lineEdit.setText(QFileDialog.getExistingDirectory(directory=prev_directory)) else: self.animationDialog.lineEdit.setText(QFileDialog.getExistingDirectory()) def sendAnimationOptions(self): path = self.animationDialog.lineEdit.text() if path == "": self.showAnimationOptions() TimeManagerProjectHandler.set_plugin_setting(conf.LAST_ANIMATION_PATH_TAG, path) delay_millis = self.animationDialog.spinBoxDelay.value() export_gif = self.animationDialog.radioAnimatedGif.isChecked() export_video = self.animationDialog.radioVideo.isChecked() do_clear = self.animationDialog.clearCheckBox.isChecked() self.signalExportVideo.emit(path, delay_millis, export_gif, export_video, do_clear) def showLabelOptions(self): options = self.labelOptions self.dialog = QDialog() lo = uic.loadUiType(os.path.join(self.path, LABEL_OPTIONS_WIDGET_FILE))[0] self.labelOptionsDialog = lo() self.labelOptionsDialog.setupUi(self.dialog) self.labelOptionsDialog.fontsize.setValue(options.size) self.labelOptionsDialog.time_format.setText(options.fmt) self.labelOptionsDialog.font.setCurrentFont(QFont(options.font)) self.labelOptionsDialog.placement.addItems(TimestampLabelConfig.PLACEMENTS) self.labelOptionsDialog.placement.setCurrentIndex(TimestampLabelConfig.PLACEMENTS.index(options.placement)) self.labelOptionsDialog.text_color.setColor(QColor(options.color)) self.labelOptionsDialog.bg_color.setColor(QColor(options.bgcolor)) self.labelOptionsDialog.buttonBox.accepted.connect(self.saveLabelOptions) self.dialog.show() def saveLabelOptions(self): self.labelOptions.font = self.labelOptionsDialog.font.currentFont().family() self.labelOptions.size = self.labelOptionsDialog.fontsize.value() self.labelOptions.bgcolor = self.labelOptionsDialog.bg_color.color().name() self.labelOptions.color = self.labelOptionsDialog.text_color.color().name() self.labelOptions.placement = self.labelOptionsDialog.placement.currentText() self.labelOptions.fmt = self.labelOptionsDialog.time_format.text() if self.labelOptionsDialog.radioButton_dt.isChecked(): self.labelOptions.type = "dt" if self.labelOptionsDialog.radioButton_beginning.isChecked(): self.labelOptions.type = "beginning" if self.labelOptionsDialog.radioButton_epoch.isChecked(): self.labelOptions.type = "epoch" def enableArchaeologyTextBox(self): self.dock.dateTimeEditCurrentTime.dateTimeChanged.connect(self.currentTimeChangedDateText) if self.bcdateSpinBox is None: self.bcdateSpinBox = self.createBCWidget(self.dock) self.bcdateSpinBox.editingFinished.connect(self.currentBCYearChanged) self.replaceWidget(self.dock.horizontalLayout, self.dock.dateTimeEditCurrentTime, self.bcdateSpinBox, 5) def getTimeWidget(self): if time_util.is_archaelogical(): return self.bcdateSpinBox else: return self.dock.dateTimeEditCurrentTime def currentBCYearChanged(self): val = self.bcdateSpinBox.text() try: bcdate_util.BCDate.from_str(val, strict_zeros=False) self.signalCurrentTimeUpdated.emit(val) except Exception as e: warn("Invalid bc date: {}".format(val)) # how to mark as such? return def disableArchaeologyTextBox(self): if self.bcdateSpinBox is None: return self.replaceWidget(self.dock.horizontalLayout, self.bcdateSpinBox, self.dock.dateTimeEditCurrentTime, 5) def createBCWidget(self, mainWidget): newWidget = QLineEdit(mainWidget) # QtGui.QSpinBox(mainWidget) # newWidget.setMinimum(-1000000) # newWidget.setValue(-1) newWidget.setText("0001 BC") return newWidget def replaceWidget(self, layout, oldWidget, newWidget, idx): """ Replaces oldWidget with newWidget at layout at index idx The way it is done, the widget is not destroyed and the connections to it remain """ layout.removeWidget(oldWidget) oldWidget.close() # I wonder if this has any memory leaks? </philosoraptor> layout.insertWidget(idx, newWidget) newWidget.show() layout.update() def optionsClicked(self): self.showOptions.emit() def exportVideoClicked(self): self.showAnimationOptions() def toggleTimeClicked(self): self.toggleTime.emit() def archaeologyClicked(self): self.toggleArchaeology.emit() def showArchOptions(self): self.archMenu = uic.loadUi(os.path.join(self.path, ARCH_WIDGET_FILE)) self.archMenu.buttonBox.accepted.connect(self.saveArchOptions) self.archMenu.buttonBox.rejected.connect(self.cancelArch) self.archMenu.show() def saveArchOptions(self): self.signalArchDigitsSpecified.emit(self.archMenu.numDigits.value()) def cancelArch(self): self.signalArchCancelled.emit() def backClicked(self): self.back.emit() def forwardClicked(self): self.forward.emit() def playClicked(self): if self.dock.pushButtonPlay.isChecked(): self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:images/pause.png")) else: self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:images/play.png")) self.play.emit() def currentTimeChangedSlider(self): sliderVal = self.dock.horizontalTimeSlider.value() try: pct = (sliderVal - self.dock.horizontalTimeSlider.minimum()) * 1.0 / ( self.dock.horizontalTimeSlider.maximum() - self.dock.horizontalTimeSlider.minimum()) except Exception as e: # slider is not properly initialized yet return if self.model.getActiveDelimitedText() and qgs.getVersion() < conf.MIN_DTEXT_FIXED: time.sleep(0.1) # hack to fix issue in qgis core with delimited text which was fixed in 2.9 self.signalSliderTimeChanged.emit(pct) def currentTimeChangedDateText(self, qdate): # info("changed time via text") self.signalCurrentTimeUpdated.emit(qdate) def currentTimeFrameTypeChanged(self, frameType): self.signalTimeFrameType.emit(frameType) def currentTimeFrameSizeChanged(self, frameSize): if frameSize < 1: # time frame size = 0 is meaningless self.dock.spinBoxTimeExtent.setValue(1) return self.signalTimeFrameSize.emit(frameSize) def unload(self): """Unload the plugin""" self.iface.removeDockWidget(self.dock) self.iface.removePluginMenu("TimeManager", self.action) def setWindowTitle(self, title): self.dock.setWindowTitle(title) def showOptionsDialog(self, layerList, animationFrameLength, playBackwards=False, loopAnimation=False): """Show the optionsDialog and populate it with settings from timeLayerManager""" # load the form self.optionsDialog = uic.loadUi(os.path.join(self.path, OPTIONS_WIDGET_FILE)) # restore settings from layerList: for layer in layerList: settings = layer_settings.getSettingsFromLayer(layer) layer_settings.addSettingsToRow(settings, self.optionsDialog.tableWidget) self.optionsDialog.tableWidget.resizeColumnsToContents() # restore animation options self.optionsDialog.spinBoxFrameLength.setValue(animationFrameLength) self.optionsDialog.checkBoxBackwards.setChecked(playBackwards) self.optionsDialog.checkBoxLabel.setChecked(self.showLabel) self.optionsDialog.checkBoxDontExportEmpty.setChecked(not self.exportEmpty) self.optionsDialog.checkBoxLoop.setChecked(loopAnimation) self.optionsDialog.show_label_options_button.clicked.connect(self.showLabelOptions) self.optionsDialog.checkBoxLabel.stateChanged.connect(self.showOrHideLabelOptions) self.optionsDialog.textBrowser.setHtml(QCoreApplication.translate('TimeManager', """\ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html> <head> <meta name="qrichtext" content="1"/> <style> li.mono { font-family: Consolas, Courier New, Courier, monospace; } </style> </head> <body> <h1>Time Manager</h1> <p>Time Manager filters your layers and displays only layers and features that match the specified time frame. Time Manager supports vector layers and raster layers (including WMS-T).</p> <p>Timestamps have to be in one of the following formats:</p> <ul> <li class="mono">%Y-%m-%d %H:%M:%S.%f</li> <li class="mono">%Y-%m-%d %H:%M:%S</li> <li class="mono">%Y-%m-%d %H:%M</li> <li class="mono">%Y-%m-%dT%H:%M:%S</li> <li class="mono">%Y-%m-%dT%H:%M:%SZ</li> <li class="mono">%Y-%m-%dT%H:%M</li> <li class="mono">%Y-%m-%dT%H:%MZ</li> <li class="mono">%Y-%m-%d</li> <li class="mono">%Y/%m/%d %H:%M:%S.%f</li> <li class="mono">%Y/%m/%d %H:%M:%S</li> <li class="mono">%Y/%m/%d %H:%M</li> <li class="mono">%Y/%m/%d</li> <li class="mono">%H:%M:%S</li> <li class="mono">%H:%M:%S.%f</li> <li class="mono">%Y.%m.%d %H:%M:%S.%f</li> <li class="mono">%Y.%m.%d %H:%M:%S</li> <li class="mono">%Y.%m.%d %H:%M</li> <li class="mono">%Y.%m.%d</li> <li class="mono">%Y%m%d%H%M%SED</li> <li>Integer timestamp in seconds after or before the epoch (1970-1-1)</li> </ul> <p>The layer list contains all layers managed by Time Manager. To add a vector layer, press [Add layer]. To add a raster layer, press [Add raster]. If you want to remove a layer from the list, select it and press [Remove layer].</p> <p>Below the layer list, you'll find the following <b>animation options</b>:</p> <p><b>Show frame for x milliseconds</b>... allows you to adjust for how long a frame will be visible during the animation</p> <p><b>Play animation backwards</b>... if checked, the animation will run in reverse direction</p> <p><b>Display frame start time on map</b>... if checked, displays the start time of the visible frame in the lower right corner of the map</p> <h2>Add Layer dialog</h2> <p>Here, you are asked to select the layer that should be added and specify the columns containing start and (optionally) end time.</p> <p>The <b>offset</b> option allows you to further time the appearance of features. If you specify an offset of -1, the features will appear one second later than they would by default.</p> <h2>Dock widget</h2> <p>The dock was designed to attach to the bottom of the QGIS main window. It offers the following tools:</p> <ul> <li><img src="images/power_on.png" alt="power"/> ... On/Off switch, allows you to turn Time Manager's functionality on/off with the click of only one button</li> <li><span class="hidden">[Settings]</span><input type="button" value="Settings"/> ... opens the Settings dialog where you can manage your spatio-temporal layers</li> <li><span class="hidden">[Export Video]</span><input type="button" value="Export Video"/> ... exports an image series based on current settings (This button is only enabled if there are layers registered in Time Manager "Settings")</li> <li><b>Time frame start: <span class="hidden">[2000-01-01 00:00:00]</span></b><input type="text" value="2000-01-01 00:00:00"/> ... shows the start time of the currently active frame. Allows you to precisely specify your desired analysis time.</li> <li><b>Time frame size: </b><input type="text" value="1"/><span class="hidden">[x]</span><select><option value="days">days</option></select> ... allow you to choose the size of the time frame</li> <li><img src="images/back.png" alt="back"/> ... go to the previous time frame</li> <li><img src="images/forward.png" alt="forward"/> ... go to the next time frame</li> <li><b>Slider</b> ... shows the position of current frame relative to the whole dataset and allows you to seamlessly scroll through the dataset</li> <li><img src="images/play.png" alt="play"/> ... start an automatic animation based on your current settings</li> </ul> </body> </html>""")) # show dialog self.showOrHideLabelOptions() self.optionsDialog.show() # create raster and vector dialogs self.vectorDialog = VectorLayerDialog(self.iface, os.path.join(self.path, ADD_VECTOR_LAYER_WIDGET_FILE), self.optionsDialog.tableWidget) self.rasterDialog = RasterLayerDialog(self.iface, os.path.join(self.path, ADD_RASTER_LAYER_WIDGET_FILE), self.optionsDialog.tableWidget) # establish connections self.optionsDialog.pushButtonAddVector.clicked.connect(self.vectorDialog.show) self.optionsDialog.pushButtonAddRaster.clicked.connect(self.rasterDialog.show) self.optionsDialog.pushButtonRemove.clicked.connect(self.removeLayer) self.optionsDialog.buttonBox.accepted.connect(self.saveOptions) # self.optionsDialog.buttonBox.helpRequested.connect(self.showHelp) def showOrHideLabelOptions(self): self.optionsDialog.show_label_options_button.setEnabled( self.optionsDialog.checkBoxLabel.isChecked()) def saveOptions(self): """Save the options from optionsDialog to timeLayerManager""" self.signalSaveOptions.emit() def removeLayer(self): """Remove currently selected layer (= row) from options""" currentRow = self.optionsDialog.tableWidget.currentRow() try: layerName = self.optionsDialog.tableWidget.item(currentRow, 0).text() except AttributeError: # if no row is selected return if QMessageBox.question(self.optionsDialog, QCoreApplication.translate("TimeManagerGuiControl", "Remove Layer"), QCoreApplication.translate("TimeManagerGuiControl", "Do you really want to remove layer {}?").format(layerName), QMessageBox.Ok, QMessageBox.Cancel) == QMessageBox.Ok: self.optionsDialog.tableWidget.removeRow(self.optionsDialog.tableWidget.currentRow()) def disableAnimationExport(self): """Disable the animation export button""" self.dock.pushButtonExportVideo.setEnabled(False) def enableAnimationExport(self): """Enable animation export button""" self.dock.pushButtonExportVideo.setEnabled(True) def refreshMapCanvas(self, sender=None): """Refresh the map canvas""" # QMessageBox.information(self.iface.mainWindow(),'Test Output','Refresh!\n'+str(sender)) self.iface.mapCanvas().refresh() def setTimeFrameSize(self, frameSize): """Set spinBoxTimeExtent to given frameSize""" self.dock.spinBoxTimeExtent.setValue(frameSize) def setTimeFrameType(self, frameType): """Set comboBoxTimeExtent to given frameType""" i = self.dock.comboBoxTimeExtent.findText(frameType) self.dock.comboBoxTimeExtent.setCurrentIndex(i) def setActive(self, isActive): """Toggle pushButtonToggleTime""" self.dock.pushButtonToggleTime.setChecked(isActive) def setArchaeologyPressed(self, isActive): """Toggle pushButtonArchaeology""" self.dock.pushButtonArchaeology.setChecked(isActive) def addActionShowSettings(self, action): """Add action to pushButttonOptions""" self.dock.pushButtonOptions.addAction(action) def turnPlayButtonOff(self): """Turn pushButtonPlay off""" if self.dock.pushButtonPlay.isChecked(): self.dock.pushButtonPlay.toggle() self.dock.pushButtonPlay.setIcon(QIcon("TimeManager:images/play.png")) def renderLabel(self, painter): """Render the current timestamp on the map canvas""" if not self.showLabel or not self.model.hasLayers() or not self.dock.pushButtonToggleTime.isChecked(): return dt = self.model.getCurrentTimePosition() if dt is None: return labelString = self.labelOptions.getLabel(dt) # Determine placement of label given cardinal directions flags = 0 for direction, flag in ('N', Qt.AlignTop), ('S', Qt.AlignBottom), ('E', Qt.AlignRight), ('W', Qt.AlignLeft): if direction in self.labelOptions.placement: flags |= flag # Get canvas dimensions width = painter.device().width() height = painter.device().height() painter.setRenderHint(painter.Antialiasing, True) txt = QTextDocument() html = """<span style="background-color:%s; padding: 5px; font-size: %spx;"> <font face="%s" color="%s">%s</font> </span> """\ % (self.labelOptions.bgcolor, self.labelOptions.size, self.labelOptions.font, self.labelOptions.color, labelString) txt.setHtml(html) layout = txt.documentLayout() size = layout.documentSize() if flags & Qt.AlignRight: x = width - 5 - size.width() elif flags & Qt.AlignLeft: x = 5 else: x = width / 2 - size.width() / 2 if flags & Qt.AlignBottom: y = height - 5 - size.height() elif flags & Qt.AlignTop: y = 5 else: y = height / 2 - size.height() / 2 painter.translate(x, y) layout.draw(painter, QAbstractTextDocumentLayout.PaintContext()) painter.translate(-x, -y) # translate back def repaintRasters(self): rasters = self.model.getActiveRasters() list([x.layer.triggerRepaint() for x in rasters]) def repaintVectors(self): list([x.layer.triggerRepaint() for x in self.model.getActiveVectors()]) def repaintJoined(self): layerIdsToRefresh = qgs.getAllJoinedLayers(set([x.layer.id() for x in self.model.getActiveVectors()])) # info("to refresh {}".format(layerIdsToRefresh)) layersToRefresh = [qgs.getLayerFromId(x) for x in layerIdsToRefresh] list([x.triggerRepaint() for x in layersToRefresh])
class ApiDimensioning(ApiParent): def __init__(self, iface, settings, controller, plugin_dir): """ Class constructor """ ApiParent.__init__(self, iface, settings, controller, plugin_dir) self.iface = iface self.settings = settings self.controller = controller self.plugin_dir = plugin_dir self.canvas = self.iface.mapCanvas() self.points = None # Snapper self.snapper_manager = SnappingConfigManager(self.iface) self.snapper_manager.set_controller(self.controller) self.snapper = self.snapper_manager.get_snapper() def open_form(self, qgis_feature=None, layer=None, db_return=None, fid=None): self.dlg_dim = DimensioningUi() self.load_settings(self.dlg_dim) # Set signals actionSnapping = self.dlg_dim.findChild(QAction, "actionSnapping") actionSnapping.triggered.connect(partial(self.snapping, actionSnapping)) self.set_icon(actionSnapping, "103") actionOrientation = self.dlg_dim.findChild(QAction, "actionOrientation") actionOrientation.triggered.connect( partial(self.orientation, actionOrientation)) self.set_icon(actionOrientation, "133") # Set layers dimensions, node and connec self.layer_dimensions = self.controller.get_layer_by_tablename( "v_edit_dimensions") self.layer_node = self.controller.get_layer_by_tablename("v_edit_node") self.layer_connec = self.controller.get_layer_by_tablename( "v_edit_connec") if qgis_feature is None: features = self.layer_dimensions.getFeatures() for feature in features: if feature['id'] == fid: return feature qgis_feature = feature #qgis_feature = self.get_feature_by_id(self.layer_dimensions, fid, 'id') self.dlg_dim.btn_accept.clicked.connect( partial(self.save_dimensioning, qgis_feature, layer)) self.dlg_dim.btn_cancel.clicked.connect( partial(self.cancel_dimensioning)) self.dlg_dim.dlg_closed.connect(partial(self.cancel_dimensioning)) self.dlg_dim.dlg_closed.connect( partial(self.save_settings, self.dlg_dim)) self.dlg_dim.key_escape.connect( partial(self.close_dialog, self.dlg_dim)) self.create_map_tips() # when funcion is called from new feature if db_return is None: extras = f'"coordinates":{{{self.points}}}' body = self.create_body(extras=extras) function_name = 'gw_fct_getdimensioning' json_result = self.controller.get_json(function_name, body) if json_result is None: return False db_return = [json_result] # get id from db response self.fid = db_return[0]['body']['feature']['id'] layout_list = [] for field in db_return[0]['body']['data']['fields']: if 'hidden' in field and field['hidden']: continue label, widget = self.set_widgets(self.dlg_dim, db_return, field) if widget.objectName() == 'id': utils_giswater.setWidgetText(self.dlg_dim, widget, self.fid) layout = self.dlg_dim.findChild(QGridLayout, field['layoutname']) # profilactic issue to prevent missed layouts againts db response and form if layout is not None: # Take the QGridLayout with the intention of adding a QSpacerItem later if layout not in layout_list and layout.objectName() not in ( 'lyt_top_1', 'lyt_bot_1', 'lyt_bot_2'): layout_list.append(layout) # Add widgets into layout layout.addWidget(label, 0, field['layoutorder']) layout.addWidget(widget, 1, field['layoutorder']) # If field is on top or bottom layout the position is horitzontal no vertical if field['layoutname'] in ('lyt_top_1', 'lyt_bot_1', 'lyt_bot_2'): layout.addWidget(label, 0, field['layoutorder']) layout.addWidget(widget, 1, field['layoutorder']) else: self.put_widgets(self.dlg_dim, field, label, widget) # Add a QSpacerItem into each QGridLayout of the list for layout in layout_list: vertical_spacer1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) layout.addItem(vertical_spacer1) title = f"DIMENSIONING - {self.fid}" self.open_dialog(self.dlg_dim, dlg_name='dimensioning', title=title) return False, False def cancel_dimensioning(self): self.iface.actionRollbackEdits().trigger() self.close_dialog(self.dlg_dim) def save_dimensioning(self, qgis_feature, layer): # Upsert feature into db layer.updateFeature(qgis_feature) layer.commitChanges() # Create body fields = '' list_widgets = self.dlg_dim.findChildren(QLineEdit) for widget in list_widgets: widget_name = widget.property('columnname') widget_value = utils_giswater.getWidgetText(self.dlg_dim, widget) if widget_value == 'null': continue fields += f'"{widget_name}":"{widget_value}", ' list_widgets = self.dlg_dim.findChildren(QCheckBox) for widget in list_widgets: widget_name = widget.property('columnname') widget_value = f'"{utils_giswater.isChecked(self.dlg_dim, widget)}"' if widget_value == 'null': continue fields += f'"{widget_name}":{widget_value},' list_widgets = self.dlg_dim.findChildren(QComboBox) for widget in list_widgets: widget_name = widget.property('columnname') widget_value = f'"{utils_giswater.get_item_data(self.dlg_dim, widget)}"' if widget_value == 'null': continue fields += f'"{widget_name}":{widget_value},' # remove last character (,) from fields fields = fields[:-1] feature = '"tableName":"v_edit_dimensions", ' feature += f'"id":"{self.fid}"' extras = f'"fields":{{{fields}}}' body = self.create_body(feature=feature, extras=extras) result = self.controller.get_json('gw_fct_setdimensioning', body) # Close dialog self.close_dialog(self.dlg_dim) def deactivate_signals(self, action): self.snapper_manager.remove_marker() try: self.canvas.xyCoordinates.disconnect() except TypeError: pass try: self.emit_point.canvasClicked.disconnect() except TypeError: pass if not action.isChecked(): action.setChecked(False) return True return False def snapping(self, action): # Set active layer and set signals self.emit_point = QgsMapToolEmitPoint(self.canvas) self.canvas.setMapTool(self.emit_point) if self.deactivate_signals(action): return self.snapper_manager.set_snapping_layers() self.snapper_manager.remove_marker() self.snapper_manager.store_snapping_options() self.snapper_manager.enable_snapping() self.snapper_manager.snap_to_node() self.snapper_manager.snap_to_connec_gully() self.snapper_manager.set_snapping_mode() self.dlg_dim.actionOrientation.setChecked(False) self.iface.setActiveLayer(self.layer_node) self.canvas.xyCoordinates.connect(self.mouse_move) self.emit_point.canvasClicked.connect( partial(self.click_button_snapping, action)) def mouse_move(self, point): # Hide marker and get coordinates self.snapper_manager.remove_marker() event_point = self.snapper_manager.get_event_point(point=point) # Snapping result = self.snapper_manager.snap_to_background_layers(event_point) if self.snapper_manager.result_is_valid(): layer = self.snapper_manager.get_snapped_layer(result) # Check feature if layer == self.layer_node or layer == self.layer_connec: self.snapper_manager.add_marker(result) def click_button_snapping(self, action, point, btn): if not self.layer_dimensions: return if btn == Qt.RightButton: if btn == Qt.RightButton: action.setChecked(False) self.deactivate_signals(action) return layer = self.layer_dimensions self.iface.setActiveLayer(layer) layer.startEditing() # Get coordinates event_point = self.snapper_manager.get_event_point(point=point) # Snapping result = self.snapper_manager.snap_to_background_layers(event_point) if self.snapper_manager.result_is_valid(): layer = self.snapper_manager.get_snapped_layer(result) # Check feature if layer == self.layer_node: feat_type = 'node' elif layer == self.layer_connec: feat_type = 'connec' else: return # Get the point snapped_feat = self.snapper_manager.get_snapped_feature(result) feature_id = self.snapper_manager.get_snapped_feature_id(result) element_id = snapped_feat.attribute(feat_type + '_id') # Leave selection layer.select([feature_id]) # Get depth of the feature fieldname = None self.project_type = self.controller.get_project_type() if self.project_type == 'ws': fieldname = "depth" elif self.project_type == 'ud' and feat_type == 'node': fieldname = "ymax" elif self.project_type == 'ud' and feat_type == 'connec': fieldname = "connec_depth" if fieldname is None: return depth = snapped_feat.attribute(fieldname) if depth: utils_giswater.setText(self.dlg_dim, "depth", depth) utils_giswater.setText(self.dlg_dim, "feature_id", element_id) utils_giswater.setText(self.dlg_dim, "feature_type", feat_type.upper()) self.snapper_manager.recover_snapping_options() self.deactivate_signals(action) action.setChecked(False) def orientation(self, action): self.emit_point = QgsMapToolEmitPoint(self.canvas) self.canvas.setMapTool(self.emit_point) if self.deactivate_signals(action): return self.snapper_manager.set_snapping_layers() self.snapper_manager.remove_marker() self.snapper_manager.store_snapping_options() self.snapper_manager.enable_snapping() self.snapper_manager.snap_to_node() self.snapper_manager.snap_to_connec_gully() self.snapper_manager.set_snapping_mode() self.dlg_dim.actionSnapping.setChecked(False) self.emit_point.canvasClicked.connect( partial(self.click_button_orientation, action)) def click_button_orientation(self, action, point, btn): if not self.layer_dimensions: return if btn == Qt.RightButton: action.setChecked(False) self.deactivate_signals(action) return self.x_symbol = self.dlg_dim.findChild(QLineEdit, "x_symbol") self.x_symbol.setText(str(int(point.x()))) self.y_symbol = self.dlg_dim.findChild(QLineEdit, "y_symbol") self.y_symbol.setText(str(int(point.y()))) self.snapper_manager.recover_snapping_options() self.deactivate_signals(action) action.setChecked(False) def create_map_tips(self): """ Create MapTips on the map """ row = self.controller.get_config('qgis_dim_tooltip') if not row or row[0].lower() != 'true': return self.timer_map_tips = QTimer(self.canvas) self.map_tip_node = QgsMapTip() self.map_tip_connec = QgsMapTip() self.canvas.xyCoordinates.connect(self.map_tip_changed) self.timer_map_tips.timeout.connect(self.show_map_tip) self.timer_map_tips_clear = QTimer(self.canvas) self.timer_map_tips_clear.timeout.connect(self.clear_map_tip) def map_tip_changed(self, point): """ SLOT. Initialize the Timer to show MapTips on the map """ if self.canvas.underMouse(): self.last_map_position = QgsPointXY(point.x(), point.y()) self.map_tip_node.clear(self.canvas) self.map_tip_connec.clear(self.canvas) self.timer_map_tips.start(100) def show_map_tip(self): """ Show MapTips on the map """ self.timer_map_tips.stop() if self.canvas.underMouse(): point_qgs = self.last_map_position point_qt = self.canvas.mouseLastXY() if self.layer_node: self.map_tip_node.showMapTip(self.layer_node, point_qgs, point_qt, self.canvas) if self.layer_connec: self.map_tip_connec.showMapTip(self.layer_connec, point_qgs, point_qt, self.canvas) self.timer_map_tips_clear.start(1000) def clear_map_tip(self): """ Clear MapTips """ self.timer_map_tips_clear.stop() self.map_tip_node.clear(self.canvas) self.map_tip_connec.clear(self.canvas) def set_widgets(self, dialog, db_return, field): widget = None label = None if field['label']: label = QLabel() label.setObjectName('lbl_' + field['widgetname']) label.setText(field['label'].capitalize()) if field['stylesheet'] is not None and 'label' in field[ 'stylesheet']: label = self.set_setStyleSheet(field, label) if 'tooltip' in field: label.setToolTip(field['tooltip']) else: label.setToolTip(field['label'].capitalize()) if field['widgettype'] == 'text' or field['widgettype'] == 'typeahead': completer = QCompleter() widget = self.add_lineedit(field) widget = self.set_widget_size(widget, field) widget = self.set_data_type(field, widget) if field['widgettype'] == 'typeahead': widget = self.manage_lineedit(field, dialog, widget, completer) elif field['widgettype'] == 'combo': widget = self.add_combobox(field) widget = self.set_widget_size(widget, field) elif field['widgettype'] == 'check': widget = self.add_checkbox(field) elif field['widgettype'] == 'datetime': widget = self.add_calendar(dialog, field) elif field['widgettype'] == 'button': widget = self.add_button(dialog, field) widget = self.set_widget_size(widget, field) elif field['widgettype'] == 'hyperlink': widget = self.add_hyperlink(field) widget = self.set_widget_size(widget, field) elif field['widgettype'] == 'hspacer': widget = self.add_horizontal_spacer() elif field['widgettype'] == 'vspacer': widget = self.add_verical_spacer() elif field['widgettype'] == 'textarea': widget = self.add_textarea(field) elif field['widgettype'] in ('spinbox'): widget = self.add_spinbox(field) elif field['widgettype'] == 'tableview': widget = self.add_tableview(db_return, field) widget = self.set_headers(widget, field) widget = self.populate_table(widget, field) widget = self.set_columns_config(widget, field['widgetname'], sort_order=1, isQStandardItemModel=True) utils_giswater.set_qtv_config(widget) widget.setObjectName(widget.property('columnname')) return label, widget
class OpenLayersOverviewWidget(QWidget, Ui_Form): def __init__(self, iface, dockwidget, olLayerTypeRegistry): QWidget.__init__(self) Ui_Form.__init__(self) self.setupUi(self) self.__canvas = iface.mapCanvas() self.__dockwidget = dockwidget self.__olLayerTypeRegistry = olLayerTypeRegistry self.__initLayerOL = False self.__fileNameImg = '' self.__srsOL = QgsCoordinateReferenceSystem( 3857, QgsCoordinateReferenceSystem.EpsgCrsId) self.__marker = MarkerCursor(self.__canvas, self.__srsOL) self.__manager = None # Need persist for PROXY bindogr.initOgr() self.__init() def __init(self): self.checkBoxHideCross.setEnabled(False) self.__populateTypeMapGUI() self.__populateButtonBox() self.__registerObjJS() self.lbStatusRead.setVisible(False) self.__setConnections() self.__timerMapReady = QTimer() self.__timerMapReady.setSingleShot(True) self.__timerMapReady.setInterval(20) self.__timerMapReady.timeout.connect(self.__checkMapReady) def __del__(self): self.__marker.reset() # Disconnect Canvas # Canvas QgsMapCanvas.extentsChanged.disconnect(self.__canvas) # Doc WidgetparentWidget QDockWidget.visibilityChanged.disconnect(self.__dockwidget) def __populateButtonBox(self): pathPlugin = "%s%s%%s" % (os.path.dirname(__file__), os.path.sep) self.pbRefresh.setIcon(QIcon(pathPlugin % "mActionDraw.png")) self.pbRefresh.setEnabled(False) self.pbAddRaster.setIcon(QIcon(pathPlugin % "mActionAddRasterLayer.png")) self.pbAddRaster.setEnabled(False) self.pbCopyKml.setIcon(QIcon(pathPlugin % "kml.png")) self.pbCopyKml.setEnabled(False) self.pbSaveImg.setIcon(QIcon(pathPlugin % "mActionSaveMapAsImage.png")) self.pbSaveImg.setEnabled(False) def __populateTypeMapGUI(self): pathPlugin = "%s%s%%s" % (os.path.dirname(__file__), os.path.sep) totalLayers = len(self.__olLayerTypeRegistry.types()) for id in range(totalLayers): layer = self.__olLayerTypeRegistry.getById(id) name = str(layer.displayName) icon = QIcon(pathPlugin % layer.groupIcon) self.comboBoxTypeMap.addItem(icon, name, id) def __setConnections(self): # Check Box self.checkBoxEnableMap.stateChanged.connect( self.__signal_checkBoxEnableMap_stateChanged) self.checkBoxHideCross.stateChanged.connect( self.__signal_checkBoxHideCross_stateChanged) # comboBoxTypeMap self.comboBoxTypeMap.currentIndexChanged.connect( self.__signal_comboBoxTypeMap_currentIndexChanged) # Canvas self.__canvas.extentsChanged.connect( self.__signal_canvas_extentsChanged) # Doc WidgetparentWidget self.__dockwidget.visibilityChanged.connect( self.__signal_DocWidget_visibilityChanged) # WebView Map self.webViewMap.page().mainFrame().javaScriptWindowObjectCleared.connect( self.__registerObjJS) # Push Button self.pbRefresh.clicked.connect( self.__signal_pbRefresh_clicked) self.pbAddRaster.clicked.connect( self.__signal_pbAddRaster_clicked) self.pbCopyKml.clicked.connect( self.__signal_pbCopyKml_clicked) self.pbSaveImg.clicked.connect( self.__signal_pbSaveImg_clicked) def __registerObjJS(self): self.webViewMap.page().mainFrame().addToJavaScriptWindowObject( "MarkerCursorQGis", self.__marker) def __signal_checkBoxEnableMap_stateChanged(self, state): enable = False if state == Qt.Unchecked: self.__marker.reset() else: if self.__canvas.layerCount() == 0: QMessageBox.warning(self, QApplication.translate( "OpenLayersOverviewWidget", "OpenLayers Overview"), QApplication.translate( "OpenLayersOverviewWidget", "At least one layer in map canvas required")) self.checkBoxEnableMap.setCheckState(Qt.Unchecked) else: enable = True if not self.__initLayerOL: self.__initLayerOL = True self.__setWebViewMap(0) else: self.__refreshMapOL() # GUI if enable: self.lbStatusRead.setVisible(False) self.webViewMap.setVisible(True) else: self.lbStatusRead.setText("") self.lbStatusRead.setVisible(True) self.webViewMap.setVisible(False) self.webViewMap.setEnabled(enable) self.comboBoxTypeMap.setEnabled(enable) self.pbRefresh.setEnabled(enable) self.pbAddRaster.setEnabled(enable) self.pbCopyKml.setEnabled(enable) self.pbSaveImg.setEnabled(enable) self.checkBoxHideCross.setEnabled(enable) def __signal_checkBoxHideCross_stateChanged(self, state): if state == Qt.Checked: self.__marker.reset() self.__marker.setVisible(False) else: self.__marker.setVisible(True) self.__refreshMapOL() def __signal_DocWidget_visibilityChanged(self, visible): if self.__canvas.layerCount() == 0: return self.checkBoxEnableMap.setCheckState(Qt.Unchecked) self.__signal_checkBoxEnableMap_stateChanged(Qt.Unchecked) def __signal_comboBoxTypeMap_currentIndexChanged(self, index): self.__setWebViewMap(index) def __signal_canvas_extentsChanged(self): if self.__canvas.layerCount() == 0 or not self.webViewMap.isVisible(): return if self.checkBoxEnableMap.checkState() == Qt.Checked: self.__refreshMapOL() def __signal_pbRefresh_clicked(self, checked): index = self.comboBoxTypeMap.currentIndex() self.__setWebViewMap(index) def __signal_pbAddRaster_clicked(self, checked): index = self.comboBoxTypeMap.currentIndex() layer = self.__olLayerTypeRegistry.getById(index) QGuiApplication.setOverrideCursor(Qt.WaitCursor) layer.addLayer() QGuiApplication.restoreOverrideCursor() def __signal_pbCopyKml_clicked(self, cheked): # Extent Openlayers action = "map.getExtent().toGeometry().toString();" wkt = self.webViewMap.page().mainFrame().evaluateJavaScript(action) rect = QgsGeometry.fromWkt(wkt).boundingBox() srsGE = QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem.EpsgCrsId) coodTrans = QgsCoordinateTransform(self.__srsOL, srsGE, QgsProject.instance()) rect = coodTrans.transform( rect, QgsCoordinateTransform.ForwardTransform) line = QgsGeometry.fromRect(rect).asPolygon()[0] wkt = str(QgsGeometry.fromPolylineXY(line).asWkt()) # Kml proj4 = str(srsGE.toProj4()) kmlLine = bindogr.exportKml(wkt, proj4) kml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"\ "<kml xmlns=\"http://www.opengis.net/kml/2.2\" " \ "xmlns:gx=\"http://www.google.com/kml/ext/2.2\" " \ "xmlns:kml=\"http://www.opengis.net/kml/2.2\" " \ "xmlns:atom=\"http://www.w3.org/2005/Atom\">" \ "<Placemark>" \ "<name>KML from Plugin Openlayers Overview for QGIS</name>" \ "<description>Extent of openlayers map from Plugin Openlayers \ Overview for QGIS</description>"\ "%s" \ "</Placemark></kml>" % kmlLine clipBoard = QApplication.clipboard() clipBoard.setText(kml) def __signal_pbSaveImg_clicked(self, cheked): if type(self.__fileNameImg) == tuple: self.__fileNameImg = self.__fileNameImg[0] fileName = QFileDialog.getSaveFileName(self, QApplication.translate( "OpenLayersOverviewWidget", "Save image"), self.__fileNameImg, QApplication.translate( "OpenLayersOverviewWidget", "Image(*.jpg)")) if not fileName == '': self.__fileNameImg = fileName else: return img = QImage(self.webViewMap.page().mainFrame().contentsSize(), QImage.Format_ARGB32_Premultiplied) imgPainter = QPainter() imgPainter.begin(img) self.webViewMap.page().mainFrame().render(imgPainter) imgPainter.end() img.save(fileName[0], "JPEG") def __signal_webViewMap_loadFinished(self, ok): if ok is False: QMessageBox.warning(self, QApplication.translate( "OpenLayersOverviewWidget", "OpenLayers Overview"), QApplication.translate( "OpenLayersOverviewWidget", "Error loading page!")) else: # wait until OpenLayers map is ready self.__checkMapReady() self.lbStatusRead.setVisible(False) self.webViewMap.setVisible(True) self.webViewMap.page().mainFrame().loadFinished.disconnect( self.__signal_webViewMap_loadFinished) def __setWebViewMap(self, id): layer = self.__olLayerTypeRegistry.getById(id) self.lbStatusRead.setText("Loading " + layer.displayName + " ...") self.lbStatusRead.setVisible(True) self.webViewMap.setVisible(False) self.webViewMap.page().mainFrame().loadFinished.connect( self.__signal_webViewMap_loadFinished) url = layer.html_url() self.webViewMap.page().mainFrame().load(QUrl(url)) def __checkMapReady(self): if self.webViewMap.page().mainFrame().evaluateJavaScript( "map != undefined"): # map ready self.__refreshMapOL() else: # wait for map self.__timerMapReady.start() def __refreshMapOL(self): # catch Exception where lat/long exceed limit of the loaded layer # the Exception name is unknown latlon = None try: latlon = self.__getCenterLongLat2OL() except Exception as e: QgsLogger().warning(e.args[0]) if latlon: action = "map.setCenter(new OpenLayers.LonLat(%f, %f));" % (latlon) self.webViewMap.page().mainFrame().evaluateJavaScript(action) action = "map.zoomToScale(%f);" % self.__canvas.scale() self.webViewMap.page().mainFrame().evaluateJavaScript(action) self.webViewMap.page().mainFrame().evaluateJavaScript( "oloMarker.changeMarker();") def __getCenterLongLat2OL(self): pntCenter = self.__canvas.extent().center() crsCanvas = self.__canvas.mapSettings().destinationCrs() if crsCanvas != self.__srsOL: coodTrans = QgsCoordinateTransform(crsCanvas, self.__srsOL, QgsProject.instance()) pntCenter = coodTrans.transform( pntCenter, QgsCoordinateTransform.ForwardTransform) return tuple([pntCenter.x(), pntCenter.y()])
class TesterWidget(BASE, WIDGET): currentTestResult = None currentTest = 0 currentTestStep = 0 BLINKING_INTERVAL = 1000 buttonColors = ["", 'QPushButton {color: yellow;}'] testingFinished = pyqtSignal() def __init__(self): super(TesterWidget, self).__init__() self.setupUi(self) self.setObjectName("TesterPluginPanel") self.btnCancel.clicked.connect(self.cancelTesting) self.btnTestOk.clicked.connect(self.testPasses) self.btnTestFailed.clicked.connect(self.testFails) self.btnRestartTest.clicked.connect(self.restartTest) self.btnSkip.clicked.connect(self.skipTest) self.btnNextStep.clicked.connect(self.runNextStep) self.buttons = [self.btnTestOk, self.btnTestFailed, self.btnNextStep] self.blinkTimer = QTimer() self.blinkTimer.timeout.connect(self._blink) def startBlinking(self): self.currentBlinkingTime = 0 self.blinkTimer.start(self.BLINKING_INTERVAL) def stopBlinking(self): self.blinkTimer.stop() for button in self.buttons: button.setStyleSheet(self.buttonColors[0]) def _blink(self): self.currentBlinkingTime += 1 color = self.buttonColors[self.currentBlinkingTime % 2] for button in self.buttons: if button.isEnabled(): button.setStyleSheet(color) def setTests(self, tests): self.tests = tests def startTesting(self): self.currentTest = 0 self.report = Report() self.runNextTest() def getReportDialog(self): """Wrapper for easy mocking""" self.reportDialog = ReportDialog(self.report) return self.reportDialog def restartTest(self): self.currentTestResult = None self.runNextTest() def runNextTest(self): if self.currentTestResult: self.report.addTestResult(self.currentTestResult) if self.currentTest < len(self.tests): test = self.tests[self.currentTest] self.labelCurrentTest.setText("Current test: %s-%s" % (test.group.upper(), test.name)) self.currentTestResult = TestResult(test) self.currentTestStep = 0 self.runNextStep() else: QApplication.restoreOverrideCursor() self.testingFinished.emit() self.setVisible(False) def runNextStep(self): self.stopBlinking() test = self.tests[self.currentTest] step = test.steps[self.currentTestStep] self.btnSkip.setEnabled(True) self.btnCancel.setEnabled(True) if os.path.exists(step.description): with open(step.description) as f: html = "".join(f.readlines()) self.webView.setHtml(html) else: if step.function is not None: self.webView.setHtml( step.description + "<p><b>[This is an automated step. Please, wait until it has been completed]</b></p>" ) else: self.webView.setHtml( step.description + "<p><b>[Click on the right-hand side buttons once you have performed this step]</b></p>" ) QCoreApplication.processEvents() if self.currentTestStep == len(test.steps) - 1: if step.function is not None: self.btnTestOk.setEnabled(False) self.btnTestFailed.setEnabled(False) self.btnNextStep.setEnabled(False) self.btnSkip.setEnabled(False) self.btnCancel.setEnabled(False) self.webView.setEnabled(False) QCoreApplication.processEvents() try: execute(step.function) self.testPasses() except Exception as e: if isinstance(e, AssertionError): self.testFails("%s\n%s" % (str(e), traceback.format_exc())) else: self.testContainsError( "%s\n%s" % (str(e), traceback.format_exc())) else: self.btnTestOk.setEnabled(True) self.btnTestOk.setText("Test passes") self.btnTestFailed.setEnabled(True) self.btnTestFailed.setText("Test fails") self.webView.setEnabled(True) self.btnNextStep.setEnabled(False) if step.prestep: try: execute(step.prestep) except Exception as e: if isinstance(e, AssertionError): self.testFailsAtSetup( "%s\n%s" % (str(e), traceback.format_exc())) else: self.testContainsError( "%s\n%s" % (str(e), traceback.format_exc())) else: if step.function is not None: self.btnTestOk.setEnabled(False) self.btnTestFailed.setEnabled(False) self.btnNextStep.setEnabled(False) self.btnSkip.setEnabled(False) self.btnCancel.setEnabled(False) self.webView.setEnabled(False) QCoreApplication.processEvents() try: execute(step.function) self.currentTestStep += 1 self.runNextStep() except Exception as e: if isinstance(e, AssertionError): self.testFails("%s\n%s" % (str(e), traceback.format_exc())) else: self.testContainsError( "%s\n%s" % (str(e), traceback.format_exc())) else: self.currentTestStep += 1 self.webView.setEnabled(True) self.btnNextStep.setEnabled(not step.isVerifyStep) if step.isVerifyStep: self.btnTestOk.setEnabled(True) self.btnTestOk.setText("Step passes") self.btnTestFailed.setEnabled(True) self.btnTestFailed.setText("Step fails") else: self.btnTestOk.setEnabled(False) self.btnTestFailed.setEnabled(False) if step.prestep: try: execute(step.prestep) except Exception as e: if isinstance(e, AssertionError): self.testFailsAtSetup( "%s\n%s" % (str(e), traceback.format_exc())) else: self.testContainsError( "%s\n%s" % (str(e), traceback.format_exc())) if step.function is None: self.startBlinking() def testPasses(self): test = self.tests[self.currentTest] if self.btnTestOk.isEnabled() and self.btnTestOk.text( ) == "Step passes": self.runNextStep() else: try: test = self.tests[self.currentTest] test.cleanup() self.currentTestResult.passed() except: self.currentTestResult.failed("Test cleanup", traceback.format_exc()) self.currentTest += 1 self.runNextTest() def testFails(self, msg=""): test = self.tests[self.currentTest] if self.btnTestOk.isEnabled() and self.btnTestOk.text( ) == "Step passes": desc = test.steps[self.currentTestStep - 1].description else: desc = test.steps[self.currentTestStep].description self.currentTestResult.failed(desc, msg) try: test.cleanup() except: pass self.currentTest += 1 self.runNextTest() def testFailsAtSetup(self, msg=""): test = self.tests[self.currentTest] if self.btnTestOk.isEnabled() and self.btnTestOk.text( ) == "Step passes": desc = test.steps[self.currentTestStep - 1].description else: desc = test.steps[self.currentTestStep].description self.currentTestResult.setupFailed(desc, msg) try: test.cleanup() except: pass self.currentTest += 1 self.runNextTest() def testContainsError(self, msg=""): test = self.tests[self.currentTest] if self.btnTestOk.isEnabled() and self.btnTestOk.text( ) == "Step passes": desc = test.steps[self.currentTestStep - 1].description else: desc = test.steps[self.currentTestStep].description self.currentTestResult.containsError(desc, msg) try: test.cleanup() except: pass self.currentTest += 1 self.runNextTest() def skipTest(self): try: test = self.tests[self.currentTest] test.cleanup() except: pass self.currentTest += 1 self.currentTestResult.skipped() self.runNextTest() def cancelTesting(self): self.setVisible(False) self.testingFinished.emit()
class QRealTime: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale//userLocale')[0:2] locale_path = os.path.join(self.plugin_dir, 'i18n', 'QRealTime_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&QRealTime') # TODO: We are going to let the user set this up in a future iteration self.toolbar = self.iface.addToolBar(u'QRealTime') self.toolbar.setObjectName(u'QRealTime') # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('QRealTime', message) def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ # Create the dialog (after translation) and keep reference self.dlg = QRealTimeDialog(self) icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = os.path.join(self.plugin_dir, 'icon.png') self.add_action(icon_path, text=self.tr(u'QRealTime Setting'), callback=self.run, parent=self.iface.mainWindow()) self.ODKMenu = QMenu('QRealTime') icon = QIcon(icon_path) self.sync = QAction(self.tr(u'sync'), self.ODKMenu) self.sync.setCheckable(True) self.sync.setChecked(False) self.sync.triggered.connect(self.download) self.sync.setChecked(False) self.iface.addCustomActionForLayerType(self.sync, 'QRealTime', QgsMapLayer.VectorLayer, True) self.Import = QAction(icon, self.tr(u'import'), self.ODKMenu) self.Import.triggered.connect(self.importData) self.iface.addCustomActionForLayerType(self.Import, 'QRealTime', QgsMapLayer.VectorLayer, True) self.makeOnline = QAction(icon, self.tr(u'Make Online'), self.ODKMenu) self.makeOnline.triggered.connect(self.sendForm) self.iface.addCustomActionForLayerType(self.makeOnline, 'QRealTime', QgsMapLayer.VectorLayer, True) service = self.dlg.getCurrentService() self.service = service self.topElement = None self.version = '' try: self.time = 1 self.time = int(service.getValue('sync time')) except: print('can not read time') self.timer = QTimer() def timeEvent(): print('calling collect data') layer = self.getLayer() print(layer) if (not self.topElement): self.topElement = layer.name() service.collectData(layer, layer.name(), False, self.topElement, self.version) self.timer.timeout.connect(timeEvent) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu(self.tr(u'QRealTime'), action) self.iface.removeToolBarIcon(action) # remove the toolbar self.iface.removeCustomActionForLayerType(self.sync) self.iface.removeCustomActionForLayerType(self.makeOnline) self.iface.removeCustomActionForLayerType(self.Import) del self.toolbar def run(self): """Run method that performs all the real work""" # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: # Do something useful here - delete the line containing pass and # substitute with your code. self.dlg.getCurrentService().setup() def importData(self): service = self.dlg.getCurrentService() layer = self.getLayer() forms, response = service.getFormList() if response.status_code == 200: self.ImportData = ImportData() self.ImportData.comboBox.addItems(forms) self.ImportData.show() result = self.ImportData.exec_() if result: selectedForm = self.ImportData.comboBox.currentText() url = service.getValue( 'url') + '//formXml?formId=' + selectedForm response = requests.request('GET', url, proxies=getProxiesConf(), verify=False) if response.status_code == 200: # with open('importForm.xml','w') as importForm: # importForm.write(response.content) self.formKey, self.topElement, self.version, self.geoField = self.updateLayer( layer, response.content) layer.setName(self.formKey) service.collectData(layer, self.formKey, True, self.topElement, self.version, self.geoField) def updateLayer(self, layer, xml): ns = '{http://www.w3.org/2002/xforms}' root = ET.fromstring(xml) #key= root[0][1][0][0].attrib['id'] instance = root[0][1].find(ns + 'instance') key = instance[0].attrib['id'] #topElement=root[0][1][0][0].tag.split('}')[1] topElement = instance[0].tag.split('}')[1] try: version = instance[0].attrib['version'] except: version = 'null' print('key captured' + key) print(root[0][1].findall(ns + 'bind')) for bind in root[0][1].findall(ns + 'bind'): attrib = bind.attrib print(attrib) fieldName = attrib['nodeset'].split('/')[-1] fieldType = attrib['type'] print('attrib type is', attrib['type']) qgstype, config = qtype(attrib['type']) print('first attribute' + fieldName) inputs = root[1].findall('.//*[@ref]') if fieldType[:3] != 'geo': print('creating new field:' + fieldName) isHidden = True for input in inputs: if fieldName == input.attrib['ref'].split('/')[-1]: isHidden = False break if isHidden: print('Reached Hidden') config['type'] = 'Hidden' self.dlg.getCurrentService().updateFields( layer, fieldName, qgstype, config) else: geoField = fieldName return key, topElement, version, geoField def getLayer(self): return self.iface.activeLayer() def sendForm(self): # get the fields model like name , widget type, options etc. version = str(datetime.date.today()) print('version is' + version) layer = self.getLayer() self.dlg.getCurrentService().updateFields(layer) fieldDict = self.getFieldsModel(layer) print('fieldDict', fieldDict) surveyDict = { "name": layer.name(), "title": layer.name(), 'VERSION': version, "instance_name": 'uuid()', "submission_url": '', "default_language": 'default', 'id_string': layer.name(), 'type': 'survey', 'children': fieldDict } survey = create_survey_element_from_dict(surveyDict) xml = survey.to_xml(validate=None, warnings=warnings) os.chdir(os.path.expanduser('~')) with open('Xform.xml', 'w') as xForm: xForm.write(xml) self.dlg.getCurrentService().sendForm(layer.name(), 'Xform.xml') def download(self, checked=False): if checked == True: self.layer = self.getLayer() self.time = int(self.service.getValue('sync time')) print('starting timer every' + str(self.time) + 'second') self.timer.start(1000 * self.time) elif checked == False: self.timer.stop() def getFieldsModel(self, currentLayer): fieldsModel = [] g_type = currentLayer.geometryType() fieldDef = { 'name': 'GEOMETRY', 'type': 'geopoint', 'bind': { 'required': 'true()' } } fieldDef['Appearance'] = 'maps' if g_type == 0: fieldDef['label'] = 'add point location' elif g_type == 1: fieldDef['label'] = 'Draw Line' fieldDef['type'] = 'geotrace' else: fieldDef['label'] = 'Draw Area' fieldDef['type'] = 'geoshape' fieldsModel.append(fieldDef) i = 0 for field in currentLayer.fields(): widget = currentLayer.editorWidgetSetup(i) fwidget = widget.type() if (fwidget == 'Hidden'): i += 1 continue fieldDef = {} fieldDef['name'] = field.name() fieldDef['map'] = field.name() fieldDef['label'] = field.alias() or field.name() fieldDef['hint'] = '' fieldDef['type'] = QVariantToODKtype(field.type()) fieldDef['bind'] = {} # fieldDef['fieldWidget'] = currentFormConfig.widgetType(i) fieldDef['fieldWidget'] = widget.type() print('getFieldModel', fieldDef['fieldWidget']) if fieldDef['fieldWidget'] in ('ValueMap', 'CheckBox', 'Photo', 'ExternalResource'): if fieldDef['fieldWidget'] == 'ValueMap': fieldDef['type'] = 'select one' valueMap = widget.config()['map'] config = {} for value in valueMap: for k, v in value.items(): config[v] = k print('configuration is ', config) choicesList = [{ 'name': name, 'label': label } for name, label in config.items()] fieldDef["choices"] = choicesList elif fieldDef['fieldWidget'] == 'Photo' or fieldDef[ 'fieldWidget'] == 'ExternalResource': fieldDef['type'] = 'image' print('got an image type field') # fieldDef['choices'] = config else: fieldDef['choices'] = {} if fieldDef['name'] == 'ODKUUID': fieldDef["bind"] = { "readonly": "true()", "calculate": "concat('uuid:', uuid())" } fieldsModel.append(fieldDef) i += 1 return fieldsModel
class Recorder(QObject): ''' classdocs ''' recordingStarted = pyqtSignal(str, bool) def __init__(self, path, interval=1000, maxLines=10000, parent=None): ''' Constructor ''' super(Recorder, self).__init__(parent) self.path = path self.filePrefix = '' self.mobiles = None self.interval = 1000 self.timer = QTimer() self.timer.timeout.connect(self.takeSnapshot) self.fileName = '' self.file = None self.lineCount = 0 self.maxLines = 10000 def setMobiles(self, mobiles): self.stopRecording() self.mobiles = mobiles def openFile(self): dt = datetime.utcnow() s = self.filePrefix + dt.strftime('%Y%m%d-%H%M%S') + '.csv' self.fileName = os.path.join(self.path, self.filePrefix, s) try: self.file = open(self.fileName, 'w') self.file.write(self.fileHeader()) self.lineCount = 0 self.recordingStarted.emit(self.fileName, True) except (IOError, FileNotFoundError): self.file = None self.recordingStarted.emit(self.fileName, False) @pyqtSlot() def startRecording(self): self.openFile() self.timer.start(self.interval) @pyqtSlot() def stopRecording(self): self.timer.stop() if self.file is not None: self.file.close() self.file = None @pyqtSlot() def takeSnapshot(self): if self.file is None: return dt = datetime.utcnow() line = dt.strftime('%d.%m.%Y\t%H:%M:%S') for v in self.mobiles.values(): lat, lon, depth, heading, altitude = v.reportPosition() line += '\t{:.9f}\t{:.9f}\t{:.1f}\t{:.1f}\t{:.1f}'.format( lat, lon, depth, heading, altitude) line += '\n' try: self.file.write(line) self.lineCount += 1 if self.lineCount > self.maxLines: self.file.close() self.openFile() except (IOError, ValueError): pass def fileHeader(self): header = 'Date\tTime' for k in self.mobiles: header = header + '\t' + k + ' Lat\t' + k + ' Lon\t' + k + ' Depth\t' + k + ' Heading\t' + k + ' Altitude' header += '\n' return header
class Create_model: def __init__(self, dlg, current_layer): """This constructor need Qt Dialog class as dlg and need current layer to execute the algorithm in current_layer parameter""" self.layers = current_layer self.bar = QProgressBar() self.dlg = dlg #self.list = [] self.border = [] self.X = [] self.Y = [] self.Z = [] self.faces = [] self.points = [] #self.list_of_all=[] self.NoData = -3.4028234663852886e+38 '''def iterator(self): """Main algorithm to iterate through all the pixels and also to create boundaries""" self.layer = self.dlg.cmbSelectLayer.currentLayer() #get the extent of layer provider = self.layer.dataProvider() extent = provider.extent() xmin = extent.xMinimum() ymax = extent.yMaximum() rows = self.layer.height() cols = self.layer.width() xsize = self.layer.rasterUnitsPerPixelX() ysize = self.layer.rasterUnitsPerPixelY() xinit = xmin + xsize / 2 yinit = ymax - ysize / 2 block = provider.block(1, extent, cols, rows) #iterate the raster to get the values of pixels k=1 for i in range(rows): for j in range(cols): x = xinit + j * xsize y = yinit k += 1 if block.value(i, j) == self.NoData: self.list_of_all.append([i,j,x,y,block.value(i,j)]) elif block.value(i,j)>=self.dlg.dsbDatum.value(): self.list.append([x, y, block.value(i, j)]) self.list_of_all.append([i,j,x,y,block.value(i,j)]) else: self.list_of_all.append([i,j,x,y,self.NoData]) xinit = xmin + xsize / 2 yinit -= ysize #get minimal value to stretching method print(len(self.list_of_all)) height=[] for searching in self.list: height.append(searching[2]) self.minimal=min(height) #iterate the raster to get the boundaries colrow=[] rowcol=[] for pixelo in self.list_of_all: rowcol.append(pixelo) if pixelo[1]==j: colrow.append(rowcol) rowcol=[] for pixel in self.list_of_all: if pixel[4] != self.NoData: if pixel[0]==0 or pixel[1]==0 or pixel[0]==i or pixel[1]==j: pixel[4] = float(self.dlg.dsbDatum.value()) self.border.append([pixel[2],pixel[3],pixel[4]]) #self.list = self.border + self.list else: wii=pixel[0] kol=pixel[1] pixel[4]=float(self.dlg.dsbDatum.value()) condition1 = colrow[wii-1][kol][4] condition2 = colrow[wii][kol-1][4] condition3 = colrow[wii+1][kol][4] condition4 = colrow[wii][kol+1][4] if condition1> self.NoData or condition2> self.NoData or condition3> self.NoData or condition4 > self.NoData: if condition1 == self.NoData or condition2== self.NoData or condition3== self.NoData or condition4 == self.NoData: self.border.append([pixel[2],pixel[3],pixel[4]]) #self.list=self.border+self.list self.list += self.border print(len(self.border)) #print(self.list) print(len(self.list)) return self.list, self.minimal''' def iterator(self): #path = iface.activeLayer().source() path = self.dlg.cmbSelectLayer.currentLayer().source() data_source = gdal.Open(path) #extract one band, because we don't need 3d matrix band = data_source.GetRasterBand(1) #read matrix as numpy array raster = band.ReadAsArray().astype(np.float) #threshold = 222 #poziom odniesienia - wyzsze od 222 threshold = self.dlg.dsbDatum.value() #change no data to nan value raster[raster == band.GetNoDataValue()] = np.nan raster2 = raster[np.logical_not( np.isnan(raster) )] #w raster2 nie mam nanow i sa to tylko wartosci wysokosci (y_index, x_index) = np.nonzero(raster >= threshold) #get the minimal value to stretching method self.minimal = np.nanmin(raster) #To demonstate this compare a.shape to band.XSize and band.YSize (upper_left_x, x_size, x_rotation, upper_left_y, y_rotation, y_size) = data_source.GetGeoTransform() x_coords = x_index * x_size + upper_left_x + ( x_size / 2) #add half the cell size y_coords = y_index * y_size + upper_left_y + (y_size / 2 ) #to centre the point raster3 = raster2[raster2 >= threshold] z_coords = np.asarray(raster3).reshape(-1) entire_matrix = np.stack((x_coords, y_coords, z_coords), axis=-1) #add outer bounder = np.pad(raster, pad_width=1, mode='constant', constant_values=np.nan) bounder_inner = (np.roll(bounder, 1, axis=0) * np.roll(bounder, -1, axis=0) * np.roll(bounder, 1, axis=1) * np.roll(bounder, -1, axis=1) * np.roll(np.roll(bounder, 1, axis=0), 1, axis=1) * np.roll(np.roll(bounder, 1, axis=0), -1, axis=1) * np.roll(np.roll(bounder, -1, axis=0), 1, axis=1) * np.roll(np.roll(bounder, -1, axis=0), -1, axis=1)) is_inner = (np.isnan(bounder_inner) == False) b = bounder b[is_inner] = np.nan b[~np.isnan(b)] = threshold boundary_real = b[1:-1, 1:-1] boundary_real_2 = boundary_real[np.logical_not( np.isnan(boundary_real))] #create boundary coordinates (y_index_boundary, x_index_boundary) = np.nonzero(boundary_real == threshold) #print(len(boundary_real_2)) x_coords_boundary = x_index_boundary * x_size + upper_left_x + ( x_size / 2) #add half the cell size y_coords_boundary = y_index_boundary * y_size + upper_left_y + ( y_size / 2) #to centre the point z_coords_boundary = np.asarray(boundary_real_2).reshape(-1) boundary_the_end = np.stack( (x_coords_boundary, y_coords_boundary, z_coords_boundary), axis=-1) boundary_the_end = np.repeat(boundary_the_end, 10, axis=0) self.entire_mat_with_heights = np.concatenate( (entire_matrix, boundary_the_end)) #entire_mat_with_heights[entire_mat_with_heights[:, [0,1,2]].argsort()] self.entire_mat_with_heights = self.entire_mat_with_heights[np.argsort( self.entire_mat_with_heights[:, 2])] return self.entire_mat_with_heights, self.minimal def delaunay(self): """This is Delaunay algorithm from Scipy lib This is needed to create vertices and faces which will be going to executed in creating STL model""" self.x = self.entire_mat_with_heights[:, 0] self.y = self.entire_mat_with_heights[:, 1] self.z = self.entire_mat_with_heights[:, 2] #print(self.x.shape) self.tri = Delaunay(np.array([self.x, self.y]).T) for vert in self.tri.simplices: self.faces.append([vert[0], vert[1], vert[2]]) for i in range(self.x.shape[0]): self.points.append([self.x[i], self.y[i], self.z[i]]) return self.faces, self.points, self.x, self.y, self.z, self.tri def saver(self): """ Create STL model """ # Define the vertices vertices = np.array(self.points) # Define the triangles facess = np.array(self.faces) # Create the mesh self.figure = mesh.Mesh( np.zeros(facess.shape[0], dtype=mesh.Mesh.dtype)) all_percentage = len(facess) value = self.bar.value() for i, f in enumerate(facess): if value < all_percentage: value = value + 1 self.bar.setValue(value) else: self.timer.stop() for j in range(3): self.figure.vectors[i][j] = vertices[f[j], :] filename = self.dlg.lineEdit.text() self.figure.save(filename) return self.figure def create_graph(self): """ Visualize model by Matplotlib lib""" fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection='3d') ax.plot_trisurf(self.x, self.y, self.z, triangles=self.tri.simplices, cmap=plt.cm.Spectral) ax.set_title('3D_Graph') ax.set_xlabel('X Label') ax.set_ylabel('Y Label') ax.set_zlabel('Z Label') plt.show() def stretching(self): """ This method stretching the entire model to the given Datum level""" for cords in self.entire_mat_with_heights: if cords[2] > self.minimal: height_stretched = cords[2] - float(self.dlg.dsbDatum.value()) height_stretched = height_stretched * self.dlg.sbStretch.value( ) height_stretched += float(self.dlg.dsbDatum.value()) cords[2] = height_stretched return self.entire_mat_with_heights def loading(self): """ Loading progress bar """ self.dialog = QProgressDialog() self.dialog.setWindowTitle("Loading") self.dialog.setLabelText("That's your progress") self.bar = QProgressBar() self.bar.setTextVisible(True) self.dialog.setBar(self.bar) self.dialog.setMinimumWidth(300) self.dialog.show() self.timer = QTimer() self.timer.timeout.connect(self.saver) self.timer.start(1000) def shape(self, direction): """ This algorithm convert ShapeFile to the .tif file. To created that process I used a lot of GDAL processing algorithms and this gave me possibility to convert the shape to .tif file, drape them to the correct elevation and merge this new raster to the current main tif """ self.plugin_dir = direction buffer_distance = self.dlg.dsbBuffer.value() output_data_type = self.dlg.sbOutputData.value() output_raster_size_unit = 0 no_data_value = 0 layer2 = self.dlg.cmbSelectShape.currentLayer() shape_dir = os.path.join(self.plugin_dir, 'TRASH') layer_ext_cor = self.dlg.cmbSelectLayer.currentLayer() provider = layer_ext_cor.dataProvider() extent = provider.extent() xmin = extent.xMinimum() ymax = extent.yMaximum() xmax = extent.xMaximum() ymin = extent.yMinimum() cord_sys = layer_ext_cor.crs().authid() coords = "%f,%f,%f,%f " % (xmin, xmax, ymin, ymax) + '[' + str(cord_sys) + ']' rows = layer_ext_cor.height() cols = layer_ext_cor.width() set_shp_height = self.dlg.sbHeight.value() processing.run( "native:buffer", { 'INPUT': layer2, 'DISTANCE': buffer_distance, 'SEGMENTS': 5, 'END_CAP_STYLE': 0, 'JOIN_STYLE': 0, 'MITER_LIMIT': 2, 'DISSOLVE': False, 'OUTPUT': os.path.join(shape_dir, 'shape_bufor.shp') }) #### tu poprawic ten dissolve \/ zredukowac ten na dole processing.run( "native:dissolve", { 'INPUT': os.path.join(shape_dir, 'shape_bufor.shp'), 'FIELD': [], 'OUTPUT': os.path.join(shape_dir, 'shape_dissolve.shp') }) processing.run( "qgis:generatepointspixelcentroidsinsidepolygons", { 'INPUT_RASTER': self.dlg.cmbSelectLayer.currentLayer().dataProvider(). dataSourceUri(), 'INPUT_VECTOR': os.path.join(shape_dir, 'shape_dissolve.shp'), 'OUTPUT': os.path.join(shape_dir, 'shape_points.shp') }) processing.run( "native:setzfromraster", { 'INPUT': os.path.join(shape_dir, 'shape_points.shp'), 'RASTER': self.dlg.cmbSelectLayer.currentLayer().dataProvider(). dataSourceUri(), 'BAND': 1, 'NODATA': 0, 'SCALE': 1, 'OUTPUT': os.path.join(shape_dir, 'shape_drape.shp') }) layer3 = iface.addVectorLayer( os.path.join(shape_dir, 'shape_drape.shp'), "Shape_Drape", "ogr") if not layer3: print("Layer failed to load!") field_name = "height" field_namess = [field.name() for field in layer3.fields()] i = 0 for l in range(100): i += 1 if field_name in field_namess: print('Exist') field_name = field_name + str(i) continue else: print('Doesn\'t Exist') break processing.run( "qgis:fieldcalculator", { 'INPUT': os.path.join(shape_dir, 'shape_drape.shp'), 'FIELD_NAME': field_name, 'FIELD_TYPE': 0, 'FIELD_LENGTH': 10, 'FIELD_PRECISION': 3, 'NEW_FIELD': True, 'FORMULA': 'z($geometry)+' + str(set_shp_height), 'OUTPUT': os.path.join(shape_dir, 'shape_drape_c.shp') }) processing.run( "gdal:rasterize", { 'INPUT': os.path.join(shape_dir, 'shape_drape_c.shp'), 'FIELD': field_name, 'BURN': 0, 'UNITS': output_raster_size_unit, 'WIDTH': cols, 'HEIGHT': rows, 'EXTENT': coords, 'NODATA': no_data_value, 'OPTIONS': '', 'DATA_TYPE': output_data_type, 'INIT': None, 'INVERT': False, 'OUTPUT': os.path.join(shape_dir, 'shape_to_raster.tif') }) iface.addRasterLayer(os.path.join(shape_dir, 'shape_to_raster.tif'), "Shape_to_Raster") QgsProject.instance().removeMapLayers([layer3.id()]) processing.run( "gdal:merge", { 'INPUT': [ self.dlg.cmbSelectLayer.currentLayer().dataProvider(). dataSourceUri(), os.path.join(shape_dir, 'shape_to_raster.tif') ], 'PCT': False, 'SEPARATE': False, 'NODATA_INPUT': None, 'NODATA_OUTPUT': None, 'OPTIONS': '', 'DATA_TYPE': 5, 'OUTPUT': os.path.join(shape_dir, 'merged.tif') }) iface.addRasterLayer(os.path.join(shape_dir, 'merged.tif'), "Raster_With_Shape") shape_to_raster = QgsProject.instance().mapLayersByName( 'Shape_to_Raster') QgsProject.instance().removeMapLayers([shape_to_raster[0].id()])
class OpenlayersController(QObject): """ Helper class that deals with QWebPage. The object lives in GUI thread, its request() slot is asynchronously called from worker thread. See https://github.com/wonder-sk/qgis-mtr-example-plugin for basic example 1. Load Web Page with OpenLayers map 2. Update OL map extend according to QGIS canvas extent """ # signal that reports to the worker thread that the image is ready finished = pyqtSignal() def __init__(self, parent, context, webPage, layerType): QObject.__init__(self, parent) debug("OpenlayersController.__init__", 3) self.context = context self.layerType = layerType self.img = QImage() self.page = webPage self.page.loadFinished.connect(self.pageLoaded) # initial size for map self.page.setViewportSize(QSize(1, 1)) self.timerMapReady = QTimer() self.timerMapReady.setSingleShot(True) self.timerMapReady.setInterval(20) self.timerMapReady.timeout.connect(self.checkMapReady) self.timer = QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self.checkMapUpdate) self.timerMax = QTimer() self.timerMax.setSingleShot(True) # TODO: different timeouts for map types self.timerMax.setInterval(2500) self.timerMax.timeout.connect(self.mapTimeout) @pyqtSlot() def request(self): debug("[GUI THREAD] Processing request", 3) self.cancelled = False if not self.page.loaded: self.init_page() else: self.setup_map() def init_page(self): url = self.layerType.html_url() debug("page file: %s" % url) self.page.mainFrame().load(QUrl(url)) # wait for page to finish loading debug("OpenlayersWorker waiting for page to load", 3) def pageLoaded(self): debug("[GUI THREAD] pageLoaded", 3) if self.cancelled: self.emitErrorImage() return # wait until OpenLayers map is ready self.checkMapReady() def checkMapReady(self): debug("[GUI THREAD] checkMapReady", 3) if self.page.mainFrame().evaluateJavaScript("map != undefined"): # map ready self.page.loaded = True self.setup_map() else: # wait for map self.timerMapReady.start() def setup_map(self): rendererContext = self.context # FIXME: self.mapSettings.outputDpi() self.outputDpi = rendererContext.painter().device().logicalDpiX() debug(" extent: %s" % rendererContext.extent().toString(), 3) debug(" center: %lf, %lf" % (rendererContext.extent().center().x(), rendererContext.extent().center().y()), 3) debug(" size: %d, %d" % ( rendererContext.painter().viewport().size().width(), rendererContext.painter().viewport().size().height()), 3) debug(" logicalDpiX: %d" % rendererContext.painter(). device().logicalDpiX(), 3) debug(" outputDpi: %lf" % self.outputDpi) debug(" mapUnitsPerPixel: %f" % rendererContext.mapToPixel(). mapUnitsPerPixel(), 3) # debug(" rasterScaleFactor: %s" % str(rendererContext. # rasterScaleFactor()), 3) # debug(" outputSize: %d, %d" % (self.iface.mapCanvas().mapRenderer(). # outputSize().width(), # self.iface.mapCanvas().mapRenderer(). # outputSize().height()), 3) # debug(" scale: %lf" % self.iface.mapCanvas().mapRenderer().scale(), # 3) # # if (self.page.lastExtent == rendererContext.extent() # and self.page.lastViewPortSize == rendererContext.painter(). # viewport().size() # and self.page.lastLogicalDpi == rendererContext.painter(). # device().logicalDpiX() # and self.page.lastOutputDpi == self.outputDpi # and self.page.lastMapUnitsPerPixel == rendererContext. # mapToPixel().mapUnitsPerPixel()): # self.renderMap() # self.finished.emit() # return self.targetSize = rendererContext.painter().viewport().size() if rendererContext.painter().device().logicalDpiX() != int(self.outputDpi): # use screen dpi for printing sizeFact = self.outputDpi / 25.4 / rendererContext.mapToPixel().mapUnitsPerPixel() self.targetSize.setWidth( rendererContext.extent().width() * sizeFact) self.targetSize.setHeight( rendererContext.extent().height() * sizeFact) debug(" targetSize: %d, %d" % ( self.targetSize.width(), self.targetSize.height()), 3) # find matching OL resolution qgisRes = rendererContext.extent().width() / self.targetSize.width() olRes = None for res in self.page.resolutions(): if qgisRes >= res: olRes = res break if olRes is None: debug("No matching OL resolution found (QGIS resolution: %f)" % qgisRes) self.emitErrorImage() return # adjust OpenLayers viewport to match QGIS extent olWidth = rendererContext.extent().width() / olRes olHeight = rendererContext.extent().height() / olRes debug(" adjust viewport: %f -> %f: %f x %f" % (qgisRes, olRes, olWidth, olHeight), 3) olSize = QSize(int(olWidth), int(olHeight)) self.page.setViewportSize(olSize) self.page.mainFrame().evaluateJavaScript("map.updateSize();") self.img = QImage(olSize, QImage.Format_ARGB32_Premultiplied) self.page.extent = rendererContext.extent() debug("map.zoomToExtent (%f, %f, %f, %f)" % ( self.page.extent.xMinimum(), self.page.extent.yMinimum(), self.page.extent.xMaximum(), self.page.extent.yMaximum()), 3) self.page.mainFrame().evaluateJavaScript( "map.zoomToExtent(new OpenLayers.Bounds(%f, %f, %f, %f), true);" % (self.page.extent.xMinimum(), self.page.extent.yMinimum(), self.page.extent.xMaximum(), self.page.extent.yMaximum())) olextent = self.page.mainFrame().evaluateJavaScript("map.getExtent();") debug("Resulting OL extent: %s" % olextent, 3) if olextent is None or not hasattr(olextent, '__getitem__'): debug("map.zoomToExtent failed") # map.setCenter and other operations throw "undefined[0]: # TypeError: 'null' is not an object" on first page load # We ignore that and render the initial map with wrong extents # self.emitErrorImage() # return else: reloffset = abs((self.page.extent.yMaximum()-olextent[ "top"])/self.page.extent.xMinimum()) debug("relative offset yMaximum %f" % reloffset, 3) if reloffset > 0.1: debug("OL extent shift failure: %s" % reloffset) self.emitErrorImage() return self.mapFinished = False self.timer.start() self.timerMax.start() def checkMapUpdate(self): if self.layerType.emitsLoadEnd: # wait for OpenLayers to finish loading loadEndOL = self.page.mainFrame().evaluateJavaScript("loadEnd") debug("waiting for loadEnd: renderingStopped=%r loadEndOL=%r" % ( self.context.renderingStopped(), loadEndOL), 4) if loadEndOL is not None: self.mapFinished = loadEndOL else: debug("OpenlayersLayer Warning: Could not get loadEnd") if self.mapFinished: self.timerMax.stop() self.timer.stop() self.renderMap() self.finished.emit() def renderMap(self): rendererContext = self.context if rendererContext.painter().device().logicalDpiX() != int(self.outputDpi): printScale = 25.4 / self.outputDpi # OL DPI to printer pixels rendererContext.painter().scale(printScale, printScale) # render OpenLayers to image painter = QPainter(self.img) self.page.mainFrame().render(painter) painter.end() if self.img.size() != self.targetSize: targetWidth = self.targetSize.width() targetHeight = self.targetSize.height() # scale using QImage for better quality debug(" scale image: %i x %i -> %i x %i" % ( self.img.width(), self.img.height(), targetWidth, targetHeight), 3) self.img = self.img.scaled(targetWidth, targetHeight, Qt.KeepAspectRatio, Qt.SmoothTransformation) # save current state self.page.lastExtent = rendererContext.extent() self.page.lastViewPortSize = rendererContext.painter().viewport().size() self.page.lastLogicalDpi = rendererContext.painter().device().logicalDpiX() self.page.lastOutputDpi = self.outputDpi self.page.lastMapUnitsPerPixel = rendererContext.mapToPixel().mapUnitsPerPixel() def mapTimeout(self): debug("mapTimeout reached") self.timer.stop() # if not self.layerType.emitsLoadEnd: self.renderMap() self.finished.emit() def emitErrorImage(self): self.img = QImage() self.targetSize = self.img.size self.finished.emit()
class TesterWidget(BASE, WIDGET): currentTestResult = None currentTest = 0 currentTestStep = 0 BLINKING_INTERVAL = 1000 buttonColors = ["", 'QPushButton {color: yellow;}'] testingFinished = pyqtSignal() def __init__(self): super(TesterWidget, self).__init__() self.setupUi(self) self.setObjectName("TesterPluginPanel") self.btnCancel.clicked.connect(self.cancelTesting) self.btnTestOk.clicked.connect(self.testPasses) self.btnTestFailed.clicked.connect(self.testFails) self.btnRestartTest.clicked.connect(self.restartTest) self.btnSkip.clicked.connect(self.skipTest) self.btnNextStep.clicked.connect(self.runNextStep) self.buttons = [self.btnTestOk, self.btnTestFailed, self.btnNextStep] self.blinkTimer = QTimer() self.blinkTimer.timeout.connect(self._blink) def startBlinking(self): self.currentBlinkingTime = 0 self.blinkTimer.start(self.BLINKING_INTERVAL) def stopBlinking(self): self.blinkTimer.stop() for button in self.buttons: button.setStyleSheet(self.buttonColors[0]) def _blink(self): self.currentBlinkingTime += 1 color = self.buttonColors[self.currentBlinkingTime % 2] for button in self.buttons: if button.isEnabled(): button.setStyleSheet(color) def setTests(self, tests): self.tests = tests def startTesting(self): self.currentTest = 0 self.report = Report() self.runNextTest() def getReportDialog(self): """Wrapper for easy mocking""" self.reportDialog = ReportDialog(self.report) return self.reportDialog def restartTest(self): self.currentTestResult = None self.runNextTest() def runNextTest(self): if self.currentTestResult: self.report.addTestResult(self.currentTestResult) if self.currentTest < len(self.tests): test = self.tests[self.currentTest] self.labelCurrentTest.setText("Current test: %s-%s" % (test.group.upper(), test.name)) self.currentTestResult = TestResult(test) self.currentTestStep = 0 self.runNextStep() else: QApplication.restoreOverrideCursor() self.testingFinished.emit() self.setVisible(False) def runNextStep(self): self.stopBlinking() test = self.tests[self.currentTest] step = test.steps[self.currentTestStep] self.btnSkip.setEnabled(True) self.btnCancel.setEnabled(True) if os.path.exists(step.description): with open(step.description) as f: html = "".join(f.readlines()) self.webView.setHtml(html) else: if step.function is not None: self.webView.setHtml(step.description + "<p><b>[This is an automated step. Please, wait until it has been completed]</b></p>") else: self.webView.setHtml(step.description + "<p><b>[Click on the right-hand side buttons once you have performed this step]</b></p>") QCoreApplication.processEvents() if self.currentTestStep == len(test.steps) - 1: if step.function is not None: self.btnTestOk.setEnabled(False) self.btnTestFailed.setEnabled(False) self.btnNextStep.setEnabled(False) self.btnSkip.setEnabled(False) self.btnCancel.setEnabled(False) self.webView.setEnabled(False) QCoreApplication.processEvents() try: execute(step.function) self.testPasses() except Exception as e: if isinstance(e, AssertionError): self.testFails("%s\n%s" % (str(e), traceback.format_exc())) else: self.testContainsError("%s\n%s" % (str(e), traceback.format_exc())) else: self.btnTestOk.setEnabled(True) self.btnTestOk.setText("Test passes") self.btnTestFailed.setEnabled(True) self.btnTestFailed.setText("Test fails") self.webView.setEnabled(True) self.btnNextStep.setEnabled(False) if step.prestep: try: execute(step.prestep) except Exception as e: if isinstance(e, AssertionError): self.testFailsAtSetup("%s\n%s" % (str(e), traceback.format_exc())) else: self.testContainsError("%s\n%s" % (str(e), traceback.format_exc())) else: if step.function is not None: self.btnTestOk.setEnabled(False) self.btnTestFailed.setEnabled(False) self.btnNextStep.setEnabled(False) self.btnSkip.setEnabled(False) self.btnCancel.setEnabled(False) self.webView.setEnabled(False) QCoreApplication.processEvents() try: execute(step.function) self.currentTestStep += 1 self.runNextStep() except Exception as e: if isinstance(e, AssertionError): self.testFails("%s\n%s" % (str(e), traceback.format_exc())) else: self.testContainsError("%s\n%s" % (str(e), traceback.format_exc())) else: self.currentTestStep += 1 self.webView.setEnabled(True) self.btnNextStep.setEnabled(not step.isVerifyStep) if step.isVerifyStep: self.btnTestOk.setEnabled(True) self.btnTestOk.setText("Step passes") self.btnTestFailed.setEnabled(True) self.btnTestFailed.setText("Step fails") else: self.btnTestOk.setEnabled(False) self.btnTestFailed.setEnabled(False) if step.prestep: try: execute(step.prestep) except Exception as e: if isinstance(e, AssertionError): self.testFailsAtSetup("%s\n%s" % (str(e), traceback.format_exc())) else: self.testContainsError("%s\n%s" % (str(e), traceback.format_exc())) if step.function is None: self.startBlinking() def testPasses(self): test = self.tests[self.currentTest] if self.btnTestOk.isEnabled() and self.btnTestOk.text() == "Step passes": self.runNextStep() else: try: test = self.tests[self.currentTest] test.cleanup() self.currentTestResult.passed() except: self.currentTestResult.failed("Test cleanup", traceback.format_exc()) self.currentTest +=1 self.runNextTest() def testFails(self, msg = ""): test = self.tests[self.currentTest] if self.btnTestOk.isEnabled() and self.btnTestOk.text() == "Step passes": desc = test.steps[self.currentTestStep - 1].description else: desc = test.steps[self.currentTestStep].description self.currentTestResult.failed(desc, msg) try: test.cleanup() except: pass self.currentTest +=1 self.runNextTest() def testFailsAtSetup(self, msg = ""): test = self.tests[self.currentTest] if self.btnTestOk.isEnabled() and self.btnTestOk.text() == "Step passes": desc = test.steps[self.currentTestStep - 1].description else: desc = test.steps[self.currentTestStep].description self.currentTestResult.setupFailed(desc, msg) try: test.cleanup() except: pass self.currentTest +=1 self.runNextTest() def testContainsError(self, msg = ""): test = self.tests[self.currentTest] if self.btnTestOk.isEnabled() and self.btnTestOk.text() == "Step passes": desc = test.steps[self.currentTestStep - 1].description else: desc = test.steps[self.currentTestStep].description self.currentTestResult.containsError(desc, msg) try: test.cleanup() except: pass self.currentTest +=1 self.runNextTest() def skipTest(self): try: test = self.tests[self.currentTest] test.cleanup() except: pass self.currentTest +=1 self.currentTestResult.skipped() self.runNextTest() def cancelTesting(self): self.setVisible(False) self.testingFinished.emit()
class LessonsCreator(object): def __init__(self, iface): self.iface = iface # add tests to tester plugin try: from qgistester.tests import addTestModule from lessonscreator.test import testerplugin addTestModule(testerplugin, "LessonsCreator") except Exception as e: pass self.capturing = False def unload(self): self.iface.removePluginMenu(u"Lessons", self.action) del self.action del self.newStepAction QgsApplication.instance().focusChanged.disconnect( self.processFocusChanged) try: from qgistester.tests import removeTestModule from lessonscreator.test import testerplugin removeTestModule(testerplugin, "LessonsCreator") except Exception as e: pass def initGui(self): lessonIcon = QIcon(os.path.dirname(__file__) + '/edit.png') self.action = QAction(lessonIcon, "Capture lesson steps", self.iface.mainWindow()) self.action.triggered.connect(self.toggleCapture) self.iface.addPluginToMenu(u"Lessons", self.action) QgsApplication.instance().focusChanged.connect( self.processFocusChanged) self.newStepAction = QAction("New step", self.iface.mainWindow()) self.newStepAction.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_W)) self.newStepAction.setShortcutContext(Qt.ApplicationShortcut) self.newStepAction.triggered.connect(self.startNewStep) self.iface.mainWindow().addAction(self.newStepAction) connections = [] iScreenshot = 0 iStep = 0 outputHtmlFile = None outputPyFile = None def startNewStep(self): if self.outputHtmlFile: self.outputHtmlFile.close() self.iStep += 1 path = os.path.join(self.folder, "step_%i.html" % self.iStep) self.outputHtmlFile = open(path, "w") self.outputPyFile.write( '''lesson.addStep("Step_%i", "step_%i.html", steptype=Step.MANUALSTEP)\n''' % (self.iStep, self.iStep)) def toggleCapture(self): if self.capturing: self.action.setText("Capture lesson steps") self.capturing = False self.outputHtmlFile.close() self.outputPyFile.close() self.outputHtmlFile = None self.outputPyFile = None else: self.folder = QFileDialog.getExistingDirectory( self.iface.mainWindow(), "Select folder to store lesson") if not self.folder: return path = os.path.join(self.folder, "__init__.py") self.outputPyFile = open(path, "w") template = ( "from lessons.lesson import Lesson, Step\n" "from lessons.utils import *\n" "lesson = Lesson('Lesson', 'Basic lessons', 'lesson.html')\n\n" ) self.outputPyFile.write(template) self.iScreenshot = 0 self.iStep = 0 self.updateConnections() self.action.setText("Stop capturing lesson steps") self.capturing = True self.startNewStep() def processWidgetClick(self, obj): if self.capturing: try: text = "Click on '%s'" % obj.text() except Exception as e: # fix_print_with_import print(e) text = "Click on " + str(obj) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.updateConnections() lastComboText = None def processComboNewSelection(self, combo): if self.capturing: text = "Select '%s' in '%s'" % (combo.currentText(), combo.objectName()) if text == self.lastComboText: return self.lastComboText = text self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(combo.parent(), combo.frameGeometry()) self.updateConnections() def processCheckBoxChange(self, check): if self.capturing: if check.isChecked(): text = "Check the '%s' checkbox" % (check.text()) else: text = "Uncheck the '%s' checkbox" % (check.text()) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(check.parent(), check.frameGeometry()) def processMenuClick(self, action): if self.capturing and action.text() != "Stop capturing lesson steps": text = "Click on menu '%s'" % action.text() self.outputHtmlFile.write("<p>%s</p>\n" % text) def getParentWindow(self, obj): window = None try: parent = obj.parent() while parent is not None: if isinstance(parent, QDialog): window = parent break parent = parent.parent() except: window = None return window or QgsApplication.instance().desktop() def processFocusChanged(self, old, new): if self.capturing: self.updateConnections() if isinstance(old, QLineEdit) and old.text().strip(): text = "Enter '%s' in textbox '%s'" % (old.text(), old.objectName()) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(old.parent(), old.frameGeometry()) else: oldParent = self.getParentWindow(old) newParent = self.getParentWindow(new) # fix_print_with_import print(oldParent, newParent) if oldParent != newParent: self.createScreenshot(newParent) elif isinstance( new, (QLineEdit, QTextEdit, QComboBox, QSpinBox, QRadioButton)): text = "Select the '%s' widget" % (old.objectName() or str(old)) self.outputHtmlFile.write("<p>%s</p>\n" % text) self.createScreenshot(new.parent(), new.frameGeometry()) timer = None def _createScreenshot(self, obj, rect): pixmap = QPixmap.grabWindow(obj.winId()).copy() if rect is not None: painter = QPainter() painter.begin(pixmap) painter.setPen(QPen(QBrush(Qt.red), 3, Qt.DashLine)) painter.drawRect(rect) painter.end() pixmap.save(os.path.join(self.folder, '%i.jpg' % self.iScreenshot), 'jpg') self.outputHtmlFile.write("<img src='%i.jpg'/>\n" % self.iScreenshot) self.iScreenshot += 1 self.timer = None def createScreenshot(self, obj, rect=None): if self.capturing and self.timer is None: self.timer = QTimer() self.timer.setInterval(1000) self.timer.setSingleShot(True) self.timer.timeout.connect( lambda: self._createScreenshot(obj, rect)) self.timer.start() def updateConnections(self): widgets = QgsApplication.instance().allWidgets() for w in widgets: if w not in self.connections: if isinstance(w, (QPushButton, QToolButton)): f = partial(self.processWidgetClick, w) w.clicked.connect(f) self.connections.append(w) elif isinstance(w, QComboBox): f = partial(self.processComboNewSelection, w) w.currentIndexChanged.connect(f) self.connections.append(w) elif isinstance(w, QCheckBox): f = partial(self.processCheckBoxChange, w) w.stateChanged.connect(f) self.connections.append(w) menuActions = [] actions = self.iface.mainWindow().menuBar().actions() for action in actions: menuActions.extend(self.getActions(action, None)) for action, menu in menuActions: if menu not in self.connections: menu.triggered.connect(self.processMenuClick) self.connections.append(menu) def getActions(self, action, menu): menuActions = [] submenu = action.menu() if submenu is None: menuActions.append((action, menu)) return menuActions else: actions = submenu.actions() for subaction in actions: if subaction.menu() is not None: menuActions.extend( self.getActions(subaction, subaction.menu())) elif not subaction.isSeparator(): menuActions.append((subaction, submenu)) return menuActions
class SettingsDialog(QDialog, DIALOG_UI): db_connection_changed = pyqtSignal(DBConnector) fetcher_task = None def __init__(self, iface=None, parent=None, qgis_utils=None): QDialog.__init__(self, parent) self.setupUi(self) self.iface = iface self.log = QgsApplication.messageLog() self._db = None self.qgis_utils = qgis_utils self.connection_is_dirty = False self.cbo_db_source.clear() self.cbo_db_source.addItem( QCoreApplication.translate("SettingsDialog", 'PostgreSQL / PostGIS'), 'pg') self.cbo_db_source.addItem( QCoreApplication.translate("SettingsDialog", 'GeoPackage'), 'gpkg') self.cbo_db_source.currentIndexChanged.connect(self.db_source_changed) self.online_models_radio_button.setChecked(True) self.online_models_radio_button.toggled.connect( self.model_provider_toggle) self.custom_model_directories_line_edit.setText("") self.custom_models_dir_button.clicked.connect( self.show_custom_model_dir) self.custom_model_directories_line_edit.setVisible(False) self.custom_models_dir_button.setVisible(False) # Set connections self.buttonBox.accepted.connect(self.accepted) self.buttonBox.helpRequested.connect(self.show_help) self.btn_test_connection.clicked.connect(self.test_connection) self.txt_pg_host.setPlaceholderText( QCoreApplication.translate( "SettingsDialog", "[Leave empty to use standard host: localhost]")) self.txt_pg_host.textEdited.connect(self.set_connection_dirty) self.txt_pg_port.setPlaceholderText( QCoreApplication.translate( "SettingsDialog", "[Leave empty to use standard port: 5432]")) self.txt_pg_port.textEdited.connect(self.set_connection_dirty) self.create_db_button.setToolTip( QCoreApplication.translate("SettingsDialog", "Create database")) self.create_db_button.clicked.connect(self.show_modal_create_db) self.selected_db_combobox.currentIndexChanged.connect( self.selected_database_changed) self.create_schema_button.setToolTip( QCoreApplication.translate("SettingsDialog", "Create schema")) self.create_schema_button.clicked.connect( self.show_modal_create_schema) self.txt_pg_user.setPlaceholderText( QCoreApplication.translate("SettingsDialog", "Database username")) self.txt_pg_user.textEdited.connect(self.set_connection_dirty) self.txt_pg_password.setPlaceholderText( QCoreApplication.translate("SettingsDialog", "[Leave empty to use system password]")) self.txt_pg_password.textEdited.connect(self.set_connection_dirty) self.txt_gpkg_file.textEdited.connect(self.set_connection_dirty) self.btn_test_service.clicked.connect(self.test_service) self.chk_use_roads.toggled.connect(self.update_images_state) # Trigger some default behaviours self.restore_settings() # Set a timer to avoid creating too many db connections while editing connection parameters self.refreshTimer = QTimer() self.refreshTimer.setSingleShot(True) self.refreshTimer.timeout.connect(self.refresh_connection) self.txt_pg_host.textChanged.connect( self.request_for_refresh_connection) self.txt_pg_port.textChanged.connect( self.request_for_refresh_connection) self.txt_pg_user.textChanged.connect( self.request_for_refresh_connection) self.txt_pg_password.textChanged.connect( self.request_for_refresh_connection) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.setLayout(QGridLayout()) self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) def showEvent(self, event): self.update_db_names() # It is necessary to reload the variables # to load the database and schema name self.restore_settings() self.selected_schema_combobox.currentIndexChanged.connect( self.selected_schema_changed) print("Conectado...") def request_for_refresh_connection(self, text): # Wait half a second before refreshing connection self.refreshTimer.start(500) def refresh_connection(self): if not self.txt_pg_user.text().strip() \ or not self.txt_pg_password.text().strip(): self.selected_db_combobox.clear() self.selected_schema_combobox.clear() else: # Update database name list self.update_db_names() def selected_database_changed(self, index): self.update_db_schemas() def selected_schema_changed(self, index): if not self.connection_is_dirty: self.connection_is_dirty = True def update_db_names(self): if self.cbo_db_source.currentData() == 'pg': dict_conn = self.read_connection_parameters() tmp_db_conn = PGConnector('') uri = tmp_db_conn.get_connection_uri(dict_conn, 'pg', level=0) dbnames = tmp_db_conn.get_dbnames_list(uri) self.selected_db_combobox.clear() if dbnames[0]: self.selected_db_combobox.addItems(dbnames[1]) else: # We won't show a message here to avoid bothering the user with potentially too much messages pass def update_db_schemas(self): if self.cbo_db_source.currentData() == 'pg': dict_conn = self.read_connection_parameters() tmp_db_conn = PGConnector('') uri = tmp_db_conn.get_connection_uri(dict_conn, 'pg') schemas_db = tmp_db_conn.get_dbname_schema_list(uri) self.selected_schema_combobox.clear() if schemas_db[0]: self.selected_schema_combobox.addItems(schemas_db[1]) else: # We won't show a message here to avoid bothering the user with potentially too much messages pass def model_provider_toggle(self): if self.offline_models_radio_button.isChecked(): self.custom_model_directories_line_edit.setVisible(True) self.custom_models_dir_button.setVisible(True) else: self.custom_model_directories_line_edit.setVisible(False) self.custom_models_dir_button.setVisible(False) self.custom_model_directories_line_edit.setText("") def get_db_connection(self, update_connection=True): if self._db is not None: self.log.logMessage("Returning existing db connection...", PLUGIN_NAME, Qgis.Info) return self._db else: self.log.logMessage("Getting new db connection...", PLUGIN_NAME, Qgis.Info) dict_conn = self.read_connection_parameters() if self.cbo_db_source.currentData() == 'pg': db = PGConnector(None, dict_conn['schema'], dict_conn) else: db = GPKGConnector(None, conn_dict=dict_conn) if update_connection: self._db = db return db def show_custom_model_dir(self): dlg = CustomModelDirDialog( self.custom_model_directories_line_edit.text(), self) dlg.exec_() def accepted(self): if self._db is not None: self._db.close_connection() self._db = None # Reset db connection self._db = self.get_db_connection() # Schema combobox changes frequently, so control whether we listen to its changes to make the db conn dirty try: self.selected_schema_combobox.currentIndexChanged.disconnect( self.selected_schema_changed) except TypeError as e: pass if self.connection_is_dirty: self.connection_is_dirty = False res, msg = self._db.test_connection() if res: self.db_connection_changed.emit(self._db) else: self.show_message(msg, Qgis.Warning) return self.save_settings() def reject(self): self.restore_settings() self.connection_is_dirty = False # Schema combobox changes frequently, so control whether we listen to its changes to make the db conn dirty try: self.selected_schema_combobox.currentIndexChanged.disconnect( self.selected_schema_changed) except TypeError as e: pass self.done(0) def set_db_connection(self, mode, dict_conn): """ To be used by external scripts and unit tests """ self.cbo_db_source.setCurrentIndex(self.cbo_db_source.findData(mode)) self.db_source_changed() if self.cbo_db_source.currentData() == 'pg': self.txt_pg_host.setText(dict_conn['host']) self.txt_pg_port.setText(dict_conn['port']) self.selected_db_combobox.clear() dbname_setting = dict_conn['database'] self.selected_db_combobox.addItem(dbname_setting) self.selected_schema_combobox.clear() schema_setting = dict_conn['schema'] self.selected_schema_combobox.addItem(schema_setting) self.txt_pg_user.setText(dict_conn['username']) self.txt_pg_password.setText(dict_conn['password']) else: self.txt_gpkg_file.setText(dict_conn['dbfile']) self.accepted() # Create/update the db object def read_connection_parameters(self): """ Convenient function to read connection parameters and apply default values if needed. """ dict_conn = dict() dict_conn['host'] = self.txt_pg_host.text().strip() or 'localhost' dict_conn['port'] = self.txt_pg_port.text().strip() or '5432' dict_conn['database'] = "'{}'".format( self.selected_db_combobox.currentText().strip()) dict_conn['schema'] = self.selected_schema_combobox.currentText( ).strip() or 'public' dict_conn['username'] = self.txt_pg_user.text().strip() dict_conn['password'] = self.txt_pg_password.text().strip() dict_conn['dbfile'] = self.txt_gpkg_file.text().strip() return dict_conn def save_settings(self): # Save QSettings dict_conn = self.read_connection_parameters() settings = QSettings() settings.setValue('Asistente-LADM_COL/db_connection_source', self.cbo_db_source.currentData()) settings.setValue('Asistente-LADM_COL/pg/host', dict_conn['host']) settings.setValue('Asistente-LADM_COL/pg/port', dict_conn['port']) settings.setValue('Asistente-LADM_COL/pg/database', dict_conn['database'].strip("'")) settings.setValue('Asistente-LADM_COL/pg/schema', dict_conn['schema']) settings.setValue('Asistente-LADM_COL/pg/username', dict_conn['username']) settings.setValue('Asistente-LADM_COL/pg/password', dict_conn['password']) settings.setValue('Asistente-LADM_COL/gpkg/dbfile', dict_conn['dbfile']) settings.setValue( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', self.offline_models_radio_button.isChecked()) if self.offline_models_radio_button.isChecked(): settings.setValue('Asistente-LADM_COL/models/custom_models', self.custom_model_directories_line_edit.text()) settings.setValue( 'Asistente-LADM_COL/quality/too_long_tolerance', int(self.txt_too_long_tolerance.text()) or DEFAULT_TOO_LONG_BOUNDARY_SEGMENTS_TOLERANCE) settings.setValue('Asistente-LADM_COL/quality/use_roads', self.chk_use_roads.isChecked()) settings.setValue( 'Asistente-LADM_COL/automatic_values/automatic_values_in_batch_mode', self.chk_automatic_values_in_batch_mode.isChecked()) settings.setValue('Asistente-LADM_COL/sources/document_repository', self.connection_box.isChecked()) endpoint = self.txt_service_endpoint.text().strip() settings.setValue( 'Asistente-LADM_COL/sources/service_endpoint', (endpoint[:-1] if endpoint.endswith('/') else endpoint) or DEFAULT_ENDPOINT_SOURCE_SERVICE) # Changes in automatic namespace or local_id configuration? current_namespace_enabled = settings.value( 'Asistente-LADM_COL/automatic_values/namespace_enabled', True, bool) current_namespace_prefix = settings.value( 'Asistente-LADM_COL/automatic_values/namespace_prefix', "") current_local_id_enabled = settings.value( 'Asistente-LADM_COL/automatic_values/local_id_enabled', True, bool) settings.setValue( 'Asistente-LADM_COL/automatic_values/namespace_enabled', self.namespace_collapsible_group_box.isChecked()) if self.namespace_collapsible_group_box.isChecked(): settings.setValue( 'Asistente-LADM_COL/automatic_values/namespace_prefix', self.txt_namespace.text()) settings.setValue( 'Asistente-LADM_COL/automatic_values/local_id_enabled', self.chk_local_id.isChecked()) if current_namespace_enabled != self.namespace_collapsible_group_box.isChecked() or \ current_namespace_prefix != self.txt_namespace.text() or \ current_local_id_enabled != self.chk_local_id.isChecked(): self.qgis_utils.automatic_namespace_local_id_configuration_changed( self._db) def restore_settings(self): # Restore QSettings settings = QSettings() self.cbo_db_source.setCurrentIndex( self.cbo_db_source.findData( settings.value('Asistente-LADM_COL/db_connection_source', 'pg'))) self.db_source_changed() self.txt_pg_host.setText(settings.value('Asistente-LADM_COL/pg/host')) self.txt_pg_port.setText(settings.value('Asistente-LADM_COL/pg/port')) dbname_setting = settings.value('Asistente-LADM_COL/pg/database') if self.selected_db_combobox.count(): index = self.selected_db_combobox.findText(dbname_setting, Qt.MatchFixedString) if index >= 0: self.selected_db_combobox.setCurrentIndex(index) else: self.selected_db_combobox.addItem(dbname_setting) schema_setting = settings.value('Asistente-LADM_COL/pg/schema') if self.selected_schema_combobox.count(): index = self.selected_schema_combobox.findText( schema_setting, Qt.MatchFixedString) if index >= 0: self.selected_schema_combobox.setCurrentIndex(index) else: self.selected_schema_combobox.addItem(schema_setting) self.txt_pg_user.setText( settings.value('Asistente-LADM_COL/pg/username')) self.txt_pg_password.setText( settings.value('Asistente-LADM_COL/pg/password')) self.txt_gpkg_file.setText( settings.value('Asistente-LADM_COL/gpkg/dbfile')) custom_model_directories_is_checked = settings.value( 'Asistente-LADM_COL/models/custom_model_directories_is_checked', type=bool) if custom_model_directories_is_checked: self.offline_models_radio_button.setChecked(True) self.custom_model_directories_line_edit.setText( settings.value('Asistente-LADM_COL/models/custom_models')) self.custom_model_directories_line_edit.setVisible(True) self.custom_models_dir_button.setVisible(True) else: self.online_models_radio_button.setChecked(True) self.custom_model_directories_line_edit.setText("") self.custom_model_directories_line_edit.setVisible(False) self.custom_models_dir_button.setVisible(False) self.txt_too_long_tolerance.setText( str( settings.value('Asistente-LADM_COL/quality/too_long_tolerance', DEFAULT_TOO_LONG_BOUNDARY_SEGMENTS_TOLERANCE))) use_roads = settings.value('Asistente-LADM_COL/quality/use_roads', True, bool) self.chk_use_roads.setChecked(use_roads) self.update_images_state(use_roads) self.chk_automatic_values_in_batch_mode.setChecked( settings.value( 'Asistente-LADM_COL/automatic_values/automatic_values_in_batch_mode', True, bool)) self.connection_box.setChecked( settings.value('Asistente-LADM_COL/sources/document_repository', True, bool)) self.namespace_collapsible_group_box.setChecked( settings.value( 'Asistente-LADM_COL/automatic_values/namespace_enabled', True, bool)) self.chk_local_id.setChecked( settings.value( 'Asistente-LADM_COL/automatic_values/local_id_enabled', True, bool)) self.txt_namespace.setText( str( settings.value( 'Asistente-LADM_COL/automatic_values/namespace_prefix', ""))) self.txt_service_endpoint.setText( settings.value('Asistente-LADM_COL/sources/service_endpoint', DEFAULT_ENDPOINT_SOURCE_SERVICE)) def db_source_changed(self): if self._db is not None: self._db.close_connection() self._db = None # Reset db connection if self.cbo_db_source.currentData() == 'pg': self.gpkg_config.setVisible(False) self.pg_config.setVisible(True) else: self.pg_config.setVisible(False) self.gpkg_config.setVisible(True) def test_connection(self): if self._db is not None: self._db.close_connection() self._db = None # Reset db connection db = self.get_db_connection(False) res, msg = db.test_connection() if db is not None: db.close_connection() self.show_message(msg, Qgis.Info if res else Qgis.Warning) self.log.logMessage("Test connection!", PLUGIN_NAME, Qgis.Info) def test_service(self): self.setEnabled(False) QCoreApplication.processEvents() res, msg = self.is_source_service_valid() self.setEnabled(True) self.show_message(msg['text'], msg['level']) def is_source_service_valid(self): res = False msg = {'text': '', 'level': Qgis.Warning} url = self.txt_service_endpoint.text().strip() if url: with OverrideCursor(Qt.WaitCursor): self.qgis_utils.status_bar_message_emitted.emit( "Checking source service availability (this might take a while)...", 0) QCoreApplication.processEvents() if self.qgis_utils.is_connected(TEST_SERVER): nam = QNetworkAccessManager() request = QNetworkRequest(QUrl(url)) reply = nam.get(request) loop = QEventLoop() reply.finished.connect(loop.quit) loop.exec_() allData = reply.readAll() response = QTextStream(allData, QIODevice.ReadOnly) status = reply.attribute( QNetworkRequest.HttpStatusCodeAttribute) if status == 200: try: data = json.loads(response.readAll()) if 'id' in data and data[ 'id'] == SOURCE_SERVICE_EXPECTED_ID: res = True msg['text'] = QCoreApplication.translate( "SettingsDialog", "The tested service is valid to upload files!" ) msg['level'] = Qgis.Info else: res = False msg['text'] = QCoreApplication.translate( "SettingsDialog", "The tested upload service is not compatible: no valid 'id' found in response." ) except json.decoder.JSONDecodeError as e: res = False msg['text'] = QCoreApplication.translate( "SettingsDialog", "Response from the tested service is not compatible: not valid JSON found." ) else: res = False msg['text'] = QCoreApplication.translate( "SettingsDialog", "There was a problem connecting to the server. The server might be down or the service cannot be reached at the given URL." ) else: res = False msg['text'] = QCoreApplication.translate( "SettingsDialog", "There was a problem connecting to Internet.") self.qgis_utils.clear_status_bar_emitted.emit() else: res = False msg['text'] = QCoreApplication.translate( "SettingsDialog", "Not valid service URL to test!") return (res, msg) def show_message(self, message, level): self.bar.pushMessage(message, level, 10) def set_connection_dirty(self, text): if not self.connection_is_dirty: self.connection_is_dirty = True def update_images_state(self, checked): self.img_with_roads.setEnabled(checked) self.img_with_roads.setToolTip( QCoreApplication.translate( "SettingsDialog", "Missing roads will be marked as errors." ) if checked else '') self.img_without_roads.setEnabled(not checked) self.img_without_roads.setToolTip( '' if checked else QCoreApplication.translate( "SettingsDialog", "Missing roads will not be marked as errors." )) def show_help(self): self.qgis_utils.show_help("settings") def database_created(self, db_name): self.update_db_names() # select the database created by the user index = self.selected_db_combobox.findText(db_name, Qt.MatchFixedString) if index >= 0: self.selected_db_combobox.setCurrentIndex(index) def schema_created(self, schema_name): self.update_db_schemas() # select the database created by the user index = self.selected_schema_combobox.findText(schema_name, Qt.MatchFixedString) if index >= 0: self.selected_schema_combobox.setCurrentIndex(index) def show_modal_create_db(self): if self.cbo_db_source.currentData() == 'pg': tmp_db_conn = PGConnector('') dict_conn = self.read_connection_parameters() uri = tmp_db_conn.get_connection_uri(dict_conn, 'pg', level=0) test_conn = tmp_db_conn.test_connection(uri=uri, level=0) if test_conn[0]: create_db_dlg = DialogGetDBOrSchemaName(dict_conn, 'database', parent=self) create_db_dlg.db_or_schema_created.connect( self.database_created) create_db_dlg.setModal(True) create_db_dlg.exec_() else: self.show_message( QCoreApplication.translate( "SettingsDialog", "First set the connection to the database before attempting to create a database." ), Qgis.Warning) def show_modal_create_schema(self): if self.cbo_db_source.currentData() == 'pg': tmp_db_conn = PGConnector('') dict_conn = self.read_connection_parameters() uri = tmp_db_conn.get_connection_uri(dict_conn, 'pg', level=0) test_conn = tmp_db_conn.test_connection(uri=uri, level=0) if test_conn[0]: create_db_dlg = DialogGetDBOrSchemaName( self.read_connection_parameters(), 'schema', parent=self) create_db_dlg.db_or_schema_created.connect(self.schema_created) create_db_dlg.setModal(True) create_db_dlg.exec_() else: self.show_message( QCoreApplication.translate( "SettingsDialog", "First set the connection to the database before attempting to create a schema." ), Qgis.Warning)
class FeatureSelectorWidget(QWidget): feature_identified = pyqtSignal(QgsFeature) def __init__(self, parent): QWidget.__init__(self, parent) edit_layout = QHBoxLayout() edit_layout.setContentsMargins(0, 0, 0, 0) edit_layout.setSpacing(2) self.setLayout(edit_layout) self.line_edit = QLineEdit(self) self.line_edit.setReadOnly(True) edit_layout.addWidget(self.line_edit) self.highlight_feature_button = QToolButton(self) self.highlight_feature_button.setPopupMode(QToolButton.MenuButtonPopup) self.highlight_feature_action = QAction( QgsApplication.getThemeIcon("/mActionHighlightFeature.svg"), "Highlight feature", self) self.scale_highlight_feature_action = QAction( QgsApplication.getThemeIcon("/mActionScaleHighlightFeature.svg"), "Scale and highlight feature", self) self.pan_highlight_feature_action = QAction( QgsApplication.getThemeIcon("/mActionPanHighlightFeature.svg"), "Pan and highlight feature", self) self.highlight_feature_button.addAction(self.highlight_feature_action) self.highlight_feature_button.addAction( self.scale_highlight_feature_action) self.highlight_feature_button.addAction( self.pan_highlight_feature_action) self.highlight_feature_button.setDefaultAction( self.highlight_feature_action) edit_layout.addWidget(self.highlight_feature_button) self.map_identification_button = QToolButton(self) self.map_identification_button.setIcon( QgsApplication.getThemeIcon("/mActionMapIdentification.svg")) self.map_identification_button.setText("Select on map") self.map_identification_button.setCheckable(True) edit_layout.addWidget(self.map_identification_button) self.map_identification_button.clicked.connect(self.map_identification) self.highlight_feature_button.triggered.connect( self.highlight_action_triggered) self.layer = None self.map_tool = None self.canvas = None self.window_widget = None self.highlight = None self.feature = QgsFeature() def set_canvas(self, map_canvas): self.map_tool = QgsMapToolIdentifyFeature(map_canvas) self.map_tool.setButton(self.map_identification_button) self.canvas = map_canvas def set_layer(self, layer): self.layer = layer def set_feature(self, feature, canvas_extent=CanvasExtent.Fixed): self.line_edit.clear() self.feature = feature if self.feature is None or not self.feature.isValid( ) or self.layer is None: return expression = QgsExpression(self.layer.displayExpression()) context = QgsExpressionContext() scope = QgsExpressionContextScope() context.appendScope(scope) scope.setFeature(feature) feature_title = expression.evaluate(context) if feature_title == "": feature_title = feature.id() self.line_edit.setText(str(feature_title)) self.highlight_feature(canvas_extent) def clear(self): self.feature = QgsFeature() self.line_edit.clear() @pyqtSlot() def map_identification(self): if self.layer is None or self.map_tool is None or self.canvas is None: return self.map_tool.setLayer(self.layer) self.canvas.setMapTool(self.map_tool) self.window_widget = QWidget.window(self) self.canvas.window().raise_() self.canvas.activateWindow() self.canvas.setFocus() self.map_tool.featureIdentified.connect( self.map_tool_feature_identified) self.map_tool.deactivated.connect(self.map_tool_deactivated) def map_tool_feature_identified(self, feature): feature = QgsFeature(feature) self.feature_identified.emit(feature) self.unset_map_tool() self.set_feature(feature) def map_tool_deactivated(self): if self.window_widget is not None: self.window_widget.raise_() self.window_widget.activateWindow() def highlight_feature(self, canvas_extent=CanvasExtent.Fixed): if self.canvas is None or not self.feature.isValid(): return geom = self.feature.geometry() if geom is None: return if canvas_extent == CanvasExtent.Scale: feature_bounding_box = geom.boundingBox() feature_bounding_box = self.canvas.mapSettings( ).layerToMapCoordinates(self.layer, feature_bounding_box) extent = self.canvas.extent() if not extent.contains(feature_bounding_box): extent.combineExtentWith(feature_bounding_box) extent.scale(1.1) self.canvas.setExtent(extent) self.canvas.refresh() elif canvas_extent == CanvasExtent.Pan: centroid = geom.centroid() center = centroid.asPoint() center = self.canvas.mapSettings().layerToMapCoordinates( self.layer, center) self.canvas.zoomByFactor(1.0, center) # refresh is done in this method # highlight self.delete_highlight() self.highlight = QgsHighlight(self.canvas, geom, self.layer) settings = QSettings() color = QColor( settings.value("/Map/highlight/color", Qgis.DEFAULT_HIGHLIGHT_COLOR.name())) alpha = int( settings.value("/Map/highlight/colorAlpha", Qgis.DEFAULT_HIGHLIGHT_COLOR.alpha())) buffer = 2 * float( settings.value("/Map/highlight/buffer", Qgis.DEFAULT_HIGHLIGHT_BUFFER_MM)) min_width = 2 * float( settings.value("/Map/highlight/min_width", Qgis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM)) self.highlight.setColor(color) # sets also fill with default alpha color.setAlpha(alpha) self.highlight.setFillColor(color) # sets fill with alpha self.highlight.setBuffer(buffer) self.highlight.setMinWidth(min_width) self.highlight.setWidth(4.0) self.highlight.show() self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.timeout.connect(self.delete_highlight) self.timer.start(3000) def delete_highlight(self): if self.highlight is not None: self.highlight.hide() del self.highlight self.highlight = None def unset_map_tool(self): if self.canvas is not None and self.map_tool is not None: # this will call mapTool.deactivated self.canvas.unsetMapTool(self.map_tool) def highlight_action_triggered(self, action): self.highlight_feature_button.setDefaultAction(action) if action == self.highlight_feature_action: self.highlight_feature() elif action == self.scale_highlight_feature_action: self.highlight_feature(CanvasExtent.Scale) elif action == self.pan_highlight_feature_action: self.highlight_feature(CanvasExtent.Pan)
class ColorsDock(QgsDockWidget): """ Custom dock widget for modfication of project colors """ def __init__(self, iface): super().__init__() self.iface = iface stack = QgsPanelWidgetStack() self.main_panel = QgsPanelWidget() self.main_panel.setDockMode(True) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setMargin(0) # pylint: disable=no-value-for-parameter self.main_panel.setLayout(layout) self.setWindowTitle(self.tr('Project Colors')) self.setObjectName('project_colors_dock') scheme = [ s for s in QgsApplication.colorSchemeRegistry().schemes() if isinstance(s, QgsProjectColorScheme) ] self.color_list = QgsColorSchemeList(None, scheme[0]) layout.addWidget(self.color_list) # defer updates for a short timeout -- prevents flooding with signals # when doing lots of color changes, improving app responsiveness self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.setInterval(100) self.block_updates = False def apply_colors(): """ Applies color changes to canvas and project """ self.block_updates = True self.color_list.saveColorsToScheme() self.block_updates = False self.iface.mapCanvas().refreshAllLayers() self.timer.timeout.connect(apply_colors) def colors_changed(): """ Triggers a deferred update of the project colors """ if self.timer.isActive(): return self.timer.start() self.color_list.model().dataChanged.connect(colors_changed) stack.setMainPanel(self.main_panel) self.setWidget(stack) QgsProject.instance().projectColorsChanged.connect( self.repopulate_list) def repopulate_list(self): """ Rebuild the colors list when project colors are changed """ if self.block_updates: return scheme = [ s for s in QgsApplication.colorSchemeRegistry().schemes() if isinstance(s, QgsProjectColorScheme) ][0] self.color_list.setScheme(scheme)
class ExportDialog(QDialog, DIALOG_UI): ValidExtensions = ['xtf', 'itf', 'gml', 'xml'] def __init__(self, base_config, parent=None): QDialog.__init__(self, parent) self.setupUi(self) QgsGui.instance().enableAutoGeometryRestore(self) self.buttonBox.accepted.disconnect() self.buttonBox.accepted.connect(self.accepted) self.buttonBox.clear() self.buttonBox.addButton(QDialogButtonBox.Cancel) self.buttonBox.addButton(self.tr('Export'), QDialogButtonBox.AcceptRole) self.buttonBox.addButton(QDialogButtonBox.Help) self.buttonBox.helpRequested.connect(self.help_requested) self.xtf_file_browse_button.clicked.connect( make_save_file_selector( self.xtf_file_line_edit, title=self.tr('Save in XTF Transfer File'), file_filter=self. tr('XTF Transfer File (*.xtf);;Interlis 1 Transfer File (*.itf);;XML (*.xml);;GML (*.gml)' ), extension='.xtf', extensions=['.' + ext for ext in self.ValidExtensions])) self.xtf_file_browse_button.clicked.connect( self.xtf_browser_opened_to_true) self.xtf_browser_was_opened = False self.gpkg_file_browse_button.clicked.connect( make_file_selector( self.gpkg_file_line_edit, title=self.tr('Open GeoPackage database file'), file_filter=self.tr('GeoPackage Database (*.gpkg)'))) self.type_combo_box.clear() self.type_combo_box.addItem(self.tr('PostGIS'), 'pg') self.type_combo_box.addItem(self.tr('GeoPackage'), 'gpkg') self.type_combo_box.currentIndexChanged.connect(self.type_changed) self.base_configuration = base_config self.restore_configuration() self.validators = Validators() nonEmptyValidator = NonEmptyStringValidator() fileValidator = FileValidator( pattern=['*.' + ext for ext in self.ValidExtensions], allow_non_existing=True) gpkgFileValidator = FileValidator(pattern='*.gpkg') self.pg_host_line_edit.setValidator(nonEmptyValidator) self.pg_database_line_edit.setValidator(nonEmptyValidator) self.pg_user_line_edit.setValidator(nonEmptyValidator) self.xtf_file_line_edit.setValidator(fileValidator) self.gpkg_file_line_edit.setValidator(gpkgFileValidator) self.pg_host_line_edit.textChanged.connect( self.validators.validate_line_edits) self.pg_host_line_edit.textChanged.emit(self.pg_host_line_edit.text()) self.pg_database_line_edit.textChanged.connect( self.validators.validate_line_edits) self.pg_database_line_edit.textChanged.emit( self.pg_database_line_edit.text()) self.pg_user_line_edit.textChanged.connect( self.validators.validate_line_edits) self.pg_user_line_edit.textChanged.emit(self.pg_user_line_edit.text()) self.xtf_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.xtf_file_line_edit.textChanged.connect( self.xtf_browser_opened_to_false) self.xtf_file_line_edit.textChanged.emit( self.xtf_file_line_edit.text()) self.gpkg_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.gpkg_file_line_edit.textChanged.emit( self.gpkg_file_line_edit.text()) #refresh the models on changing values but avoid massive db connects by timer self.refreshTimer = QTimer() self.refreshTimer.setSingleShot(True) self.refreshTimer.timeout.connect(self.refresh_models) self.pg_host_line_edit.textChanged.connect( self.request_for_refresh_models) self.pg_port_line_edit.textChanged.connect( self.request_for_refresh_models) self.pg_database_line_edit.textChanged.connect( self.request_for_refresh_models) self.pg_schema_line_edit.textChanged.connect( self.request_for_refresh_models) self.pg_user_line_edit.textChanged.connect( self.request_for_refresh_models) self.pg_password_line_edit.textChanged.connect( self.request_for_refresh_models) self.gpkg_file_line_edit.textChanged.connect( self.request_for_refresh_models) self.export_models_model = ExportModels(None, None, None) self.refreshed_export_models_model() self.export_models_view.setModel(self.export_models_model) self.export_models_view.clicked.connect(self.export_models_model.check) self.export_models_view.space_pressed.connect( self.export_models_model.check) def request_for_refresh_models(self): # hold refresh back self.refreshTimer.start(500) def refresh_models(self): self.export_models_model = self.refreshed_export_models_model() self.export_models_view.setModel(self.export_models_model) self.export_models_view.clicked.connect(self.export_models_model.check) self.export_models_view.space_pressed.connect( self.export_models_model.check) def refreshed_export_models_model(self): tool_name = 'ili2pg' if self.type_combo_box.currentData( ) == 'pg' else 'ili2gpkg' uri = [] if tool_name == 'ili2pg': uri += ['dbname={}'.format(self.updated_configuration().database)] uri += ['user={}'.format(self.updated_configuration().dbusr)] if self.updated_configuration().dbpwd: uri += [ 'password={}'.format(self.updated_configuration().dbpwd) ] uri += ['host={}'.format(self.updated_configuration().dbhost)] if self.updated_configuration().dbport: uri += ['port={}'.format(self.updated_configuration().dbport)] elif tool_name == 'ili2gpkg': uri = [self.updated_configuration().dbfile] uri_string = ' '.join(uri) schema = self.updated_configuration().dbschema self.export_models_model = ExportModels(tool_name, uri_string, schema) return self.export_models_model def accepted(self): configuration = self.updated_configuration() if not self.xtf_file_line_edit.validator().validate( configuration.xtffile, 0)[0] == QValidator.Acceptable: self.txtStdout.setText( self. tr('Please set a valid INTERLIS XTF file before exporting data.' )) self.xtf_file_line_edit.setFocus() return if not configuration.iliexportmodels: self.txtStdout.setText( self.tr('Please set a model before exporting data.')) self.export_models_view.setFocus() return if self.type_combo_box.currentData() == 'pg': if not configuration.dbhost: self.txtStdout.setText( self.tr('Please set a host before exporting data.')) self.pg_host_line_edit.setFocus() return if not configuration.database: self.txtStdout.setText( self.tr('Please set a database before exporting data.')) self.pg_database_line_edit.setFocus() return if not configuration.dbusr: self.txtStdout.setText( self.tr( 'Please set a database user before exporting data.')) self.pg_user_line_edit.setFocus() return elif self.type_combo_box.currentData() == 'gpkg': if not configuration.dbfile or self.gpkg_file_line_edit.validator( ).validate(configuration.dbfile, 0)[0] != QValidator.Acceptable: self.txtStdout.setText( self. tr('Please set an existing database file before creating the project.' )) self.gpkg_file_line_edit.setFocus() return # If xtf browser was opened and the file exists, the user already chose # to overwrite the file if os.path.isfile(self.xtf_file_line_edit.text().strip() ) and not self.xtf_browser_was_opened: self.msg = QMessageBox() self.msg.setIcon(QMessageBox.Warning) self.msg.setText( self.tr( "{filename} already exists.\nDo you want to replace it?"). format(filename=os.path.basename( self.xtf_file_line_edit.text().strip()))) self.msg.setWindowTitle(self.tr("Save in XTF Transfer File")) self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box = self.msg.exec_() if msg_box == QMessageBox.No: return with OverrideCursor(Qt.WaitCursor): self.progress_bar.show() self.progress_bar.setValue(0) self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() exporter = iliexporter.Exporter() tool_name = 'ili2pg' if self.type_combo_box.currentData( ) == 'pg' else 'ili2gpkg' exporter.tool_name = tool_name exporter.configuration = configuration self.save_configuration(configuration) exporter.stdout.connect(self.print_info) exporter.stderr.connect(self.on_stderr) exporter.process_started.connect(self.on_process_started) exporter.process_finished.connect(self.on_process_finished) self.progress_bar.setValue(25) try: if exporter.run() != iliexporter.Exporter.SUCCESS: self.enable() self.progress_bar.hide() return except JavaNotFoundError: self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText( self. tr('Java could not be found. Please <a href="https://java.com/en/download/">install Java</a> and or <a href="#configure">configure a custom java path</a>. We also support the JAVA_HOME environment variable in case you prefer this.' )) self.enable() self.progress_bar.hide() return self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) self.progress_bar.setValue(100) def print_info(self, text): self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.append(text) QCoreApplication.processEvents() def on_stderr(self, text): color_log_text(text, self.txtStdout) self.advance_progress_bar_by_text(text) QCoreApplication.processEvents() def on_process_started(self, command): self.disable() self.txtStdout.setTextColor(QColor('#000000')) self.txtStdout.clear() self.txtStdout.setText(command) QCoreApplication.processEvents() def on_process_finished(self, exit_code, result): color = '#004905' if exit_code == 0 else '#aa2222' self.txtStdout.setTextColor(QColor(color)) self.txtStdout.append(self.tr('Finished ({})'.format(exit_code))) if result == iliexporter.Exporter.SUCCESS: self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) else: self.enable() def updated_configuration(self): """ Get the configuration that is updated with the user configuration changes on the dialog. :return: Configuration """ configuration = ili2dbconfig.ExportConfiguration() if self.type_combo_box.currentData() == 'pg': # PostgreSQL specific options configuration.dbhost = self.pg_host_line_edit.text().strip() configuration.dbport = self.pg_port_line_edit.text().strip() configuration.dbusr = self.pg_user_line_edit.text().strip() configuration.database = self.pg_database_line_edit.text().strip() configuration.dbschema = self.pg_schema_line_edit.text().strip( ).lower() configuration.dbpwd = self.pg_password_line_edit.text() elif self.type_combo_box.currentData() == 'gpkg': configuration.dbfile = self.gpkg_file_line_edit.text().strip() configuration.xtffile = self.xtf_file_line_edit.text().strip() configuration.iliexportmodels = ';'.join( self.export_models_model.checked_models()) configuration.ilimodels = ';'.join( self.export_models_model.stringList()) configuration.base_configuration = self.base_configuration return configuration def save_configuration(self, configuration): settings = QSettings() settings.setValue('QgsProjectGenerator/ili2pg/xtffile_export', configuration.xtffile) settings.setValue('QgsProjectGenerator/importtype', self.type_combo_box.currentData()) if self.type_combo_box.currentData() in ['ili2pg', 'pg']: # PostgreSQL specific options settings.setValue('QgsProjectGenerator/ili2pg/host', configuration.dbhost) settings.setValue('QgsProjectGenerator/ili2pg/port', configuration.dbport) settings.setValue('QgsProjectGenerator/ili2pg/user', configuration.dbusr) settings.setValue('QgsProjectGenerator/ili2pg/database', configuration.database) settings.setValue('QgsProjectGenerator/ili2pg/schema', configuration.dbschema) settings.setValue('QgsProjectGenerator/ili2pg/password', configuration.dbpwd) elif self.type_combo_box.currentData() in ['ili2gpkg', 'gpkg']: settings.setValue('QgsProjectGenerator/ili2gpkg/dbfile', configuration.dbfile) def restore_configuration(self): settings = QSettings() self.xtf_file_line_edit.setText( settings.value('QgsProjectGenerator/ili2pg/xtffile_export')) self.pg_host_line_edit.setText( settings.value('QgsProjectGenerator/ili2pg/host', 'localhost')) self.pg_port_line_edit.setText( settings.value('QgsProjectGenerator/ili2pg/port')) self.pg_user_line_edit.setText( settings.value('QgsProjectGenerator/ili2pg/user')) self.pg_database_line_edit.setText( settings.value('QgsProjectGenerator/ili2pg/database')) self.pg_schema_line_edit.setText( settings.value('QgsProjectGenerator/ili2pg/schema')) self.pg_password_line_edit.setText( settings.value('QgsProjectGenerator/ili2pg/password')) self.gpkg_file_line_edit.setText( settings.value('QgsProjectGenerator/ili2gpkg/dbfile')) mode = settings.value('QgsProjectGenerator/importtype', 'pg') mode = 'pg' if mode == 'ili2pg' else mode mode = 'gpkg' if mode == 'ili2gpkg' else mode self.type_combo_box.setCurrentIndex(self.type_combo_box.findData(mode)) self.type_changed() def disable(self): self.pg_config.setEnabled(False) self.ili_config.setEnabled(False) self.buttonBox.setEnabled(False) def enable(self): self.pg_config.setEnabled(True) self.ili_config.setEnabled(True) self.buttonBox.setEnabled(True) def type_changed(self): self.progress_bar.hide() if self.type_combo_box.currentData() == 'pg': self.pg_config.show() self.gpkg_config.hide() elif self.type_combo_box.currentData() == 'gpkg': self.pg_config.hide() self.gpkg_config.show() def link_activated(self, link): if link.url() == '#configure': cfg = OptionsDialog(self.base_configuration) if cfg.exec_(): settings = QSettings() settings.beginGroup('QgsProjectGenerator/ili2db') self.base_configuration.save(settings) else: QDesktopServices.openUrl(link) def help_requested(self): os_language = QLocale( QSettings().value('locale/userLocale')).name()[:2] if os_language in ['es', 'de']: webbrowser.open( "https://opengisch.github.io/projectgenerator/docs/{}/user-guide.html#export-an-interlis-transfer-file-xtf" .format(os_language)) else: webbrowser.open( "https://opengisch.github.io/projectgenerator/docs/user-guide.html#export-an-interlis-transfer-file-xtf" ) def xtf_browser_opened_to_true(self): """ Slot. Sets a flag to true to eventually avoid asking a user whether to overwrite a file. """ self.xtf_browser_was_opened = True def xtf_browser_opened_to_false(self): """ Slot. Sets a flag to false to eventually ask a user whether to overwrite a file. """ self.xtf_browser_was_opened = False def advance_progress_bar_by_text(self, text): if text.strip() == 'Info: compile models…': self.progress_bar.setValue(50) elif text.strip() == 'Info: create table structure…': self.progress_bar.setValue(75)
class QRealTime: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale try: if QSettings().value('locale//overrideFlag'): locale = QSettings().value('locale//globalLocale')[0:2] else: locale = QSettings().value('locale//userLocale')[0:2] except: locale = 'en' locale_path = os.path.join(self.plugin_dir, 'i18n', 'QRealTime_{}.qm'.format(locale)) print(locale_path) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = 'QRealTime' # TODO: We are going to let the user set this up in a future iteration self.toolbar = self.iface.addToolBar('QRealTime') self.toolbar.setObjectName('QRealTime') # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('QRealTime', message) def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ # Create the dialog (after translation) and keep reference self.dlg = QRealTimeDialog(self) icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) return action def add_layer_action(self, icon_path, text, callback, icon_enabled=True, add_to_vLayer=True, enabled_flag=True, parent=None): icon = QIcon(icon_path) if icon_enabled: action = QAction(icon, text, parent) else: action = QAction(text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if add_to_vLayer: self.iface.addCustomActionForLayerType(action, 'QRealTime', QgsMapLayer.VectorLayer, True) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = os.path.join(self.plugin_dir, 'icon.png') self.add_action(icon_path, text=self.tr(u'QRealTime Setting'), callback=self.run, parent=self.iface.mainWindow()) """add sync action""" self.sync = self.add_layer_action(icon_path, self.tr(u'sync'), self.download, False) # make sync action checkable and default to unchecked self.sync.setCheckable(True) self.sync.setChecked(False) """add import action """ self.Import = self.add_layer_action(icon_path, self.tr(u'import'), self.importData) """add makeonline action """ self.makeOnline = self.add_layer_action(icon_path, self.tr(u'Make Online'), self.sendForm) service = self.dlg.getCurrentService() self.service = service self.topElement = None self.version = '' try: self.time = 86400 self.time = int(service.getValue(self.tr('sync time'))) except: print('can not read time') self.timer = QTimer() def timeEvent(): print('calling collect data') self.service.importData(self.layer, self.formID, False) self.timer.timeout.connect(timeEvent) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu('QRealTime', action) self.iface.removeCustomActionForLayerType(action) self.iface.removeToolBarIcon(action) # remove the toolbar del self.toolbar def run(self): """Run method that performs all the real work""" # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: # Do something useful here - delete the line containing pass and # substitute with your code. self.dlg.getCurrentService().setup() def importData(self): service = self.dlg.getCurrentService() layer = self.getLayer() forms, response = service.getFormList() if response: if response.status_code == 200: self.ImportData = ImportData() for name, key in forms.items(): self.ImportData.comboBox.addItem(name, key) self.ImportData.show() result = self.ImportData.exec_() if result: selectedForm = self.ImportData.comboBox.currentData() service.importData(layer, selectedForm, True) def getLayer(self): return self.iface.activeLayer() def sendForm(self): # get the fields model like name , widget type, options etc. layer = self.getLayer() service = self.dlg.getCurrentService() service.prepareSendForm(layer) def download(self, checked=False): if checked == True: self.layer = self.getLayer() self.service = self.dlg.getCurrentService() forms, response = self.service.getFormList() if response: self.formID = forms[self.layer.name()] try: self.time = int(self.service.getValue( self.tr('sync time'))) except: self.time = 3600 print('starting timer every' + str(self.time) + 'second') self.timer.start(1000 * self.time) elif checked == False: self.timer.stop() print("timer stoped")
class ExportDialog(QDialog, DIALOG_UI): ValidExtensions = ["xtf", "XTF", "itf", "ITF", "gml", "GML", "xml", "XML"] def __init__(self, base_config, parent=None): QDialog.__init__(self, parent) self.setupUi(self) self.db_simple_factory = DbSimpleFactory() QgsGui.instance().enableAutoGeometryRestore(self) self.buttonBox.accepted.disconnect() self.buttonBox.clear() self.buttonBox.addButton(QDialogButtonBox.Cancel) self.buttonBox.addButton(QDialogButtonBox.Help) self.buttonBox.helpRequested.connect(self.help_requested) self.export_text = self.tr("Export") self.set_button_to_export_action = QAction(self.export_text, None) self.set_button_to_export_action.triggered.connect( self.set_button_to_export) self.export_without_validation_text = self.tr( "Export without validation") self.set_button_to_export_without_validation_action = QAction( self.export_without_validation_text, None) self.set_button_to_export_without_validation_action.triggered.connect( self.set_button_to_export_without_validation) self.edit_command_action = QAction(self.tr("Edit ili2db command"), None) self.edit_command_action.triggered.connect(self.edit_command) self.export_tool_button.addAction( self.set_button_to_export_without_validation_action) self.export_tool_button.addAction(self.edit_command_action) self.export_tool_button.setText(self.export_text) self.export_tool_button.clicked.connect(self.accepted) self.xtf_file_browse_button.clicked.connect( make_save_file_selector( self.xtf_file_line_edit, title=self.tr("Save in XTF Transfer File"), file_filter=self. tr("XTF Transfer File (*.xtf *XTF);;Interlis 1 Transfer File (*.itf *ITF);;XML (*.xml *XML);;GML (*.gml *GML)" ), extension=".xtf", extensions=["." + ext for ext in self.ValidExtensions], )) self.xtf_file_browse_button.clicked.connect( self.xtf_browser_opened_to_true) self.xtf_browser_was_opened = False self.type_combo_box.clear() self._lst_panel = dict() for db_id in self.db_simple_factory.get_db_list(False): self.type_combo_box.addItem(displayDbIliMode[db_id], db_id) db_factory = self.db_simple_factory.create_factory(db_id) item_panel = db_factory.get_config_panel(self, DbActionType.EXPORT) self._lst_panel[db_id] = item_panel self.db_layout.addWidget(item_panel) self.validators = Validators() fileValidator = FileValidator( pattern=["*." + ext for ext in self.ValidExtensions], allow_non_existing=True, ) self.xtf_file_line_edit.setValidator(fileValidator) self.xtf_file_line_edit.textChanged.connect( self.validators.validate_line_edits) self.xtf_file_line_edit.textChanged.connect( self.xtf_browser_opened_to_false) self.xtf_file_line_edit.textChanged.emit( self.xtf_file_line_edit.text()) # Reset to export as default text self.xtf_file_line_edit.textChanged.connect(self.set_button_to_export) # refresh the models on changing values but avoid massive db connects by timer self.refreshTimer = QTimer() self.refreshTimer.setSingleShot(True) self.refreshTimer.timeout.connect(self.refresh_models) for key, value in self._lst_panel.items(): value.notify_fields_modified.connect( self.request_for_refresh_models) self.validate_data = True # validates exported data by default, We use --disableValidation when is False self.base_configuration = base_config self.restore_configuration() self.export_models_model = ExportModels() self.export_models_view.setModel(self.export_models_model) self.export_models_view.clicked.connect(self.export_models_model.check) self.export_models_view.space_pressed.connect( self.export_models_model.check) self.request_for_refresh_models() self.type_combo_box.currentIndexChanged.connect(self.type_changed) self.bar = QgsMessageBar() self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.txtStdout.setLayout(QGridLayout()) self.txtStdout.layout().setContentsMargins(0, 0, 0, 0) self.txtStdout.layout().addWidget(self.bar, 0, 0, Qt.AlignTop) def request_for_refresh_models(self): # hold refresh back self.refreshTimer.start(500) def refresh_models(self): self.refreshed_export_models_model() def refreshed_export_models_model(self): tool = self.type_combo_box.currentData() & ~DbIliMode.ili configuration = self.updated_configuration() schema = configuration.dbschema db_factory = self.db_simple_factory.create_factory(tool) config_manager = db_factory.get_db_command_config_manager( configuration) uri_string = config_manager.get_uri(configuration.db_use_super_login) db_connector = None try: db_connector = db_factory.get_db_connector(uri_string, schema) except (DBConnectorError, FileNotFoundError): # when wrong connection parameters entered, there should just be returned an empty model - so let it pass pass self.export_models_model.refresh_models(db_connector) def db_ili_version(self, configuration): """ Returns the ili2db version the database has been created with or None if the database could not be detected as a ili2db database """ schema = configuration.dbschema db_factory = self.db_simple_factory.create_factory(configuration.tool) config_manager = db_factory.get_db_command_config_manager( configuration) uri_string = config_manager.get_uri(configuration.db_use_super_login) db_connector = None try: db_connector = db_factory.get_db_connector(uri_string, schema) return db_connector.ili_version() except (DBConnectorError, FileNotFoundError): return None def set_button_to_export(self): """ Changes the text of the button to export (with validation) and sets the validate_data to true. So on clicking the button the export will start with validation. The buttons actions are changed to be able to switch the without-validation mode. """ self.validate_data = True self.export_tool_button.removeAction(self.set_button_to_export_action) self.export_tool_button.removeAction(self.edit_command_action) self.export_tool_button.addAction( self.set_button_to_export_without_validation_action) self.export_tool_button.addAction(self.edit_command_action) self.export_tool_button.setText(self.export_text) def set_button_to_export_without_validation(self): """ Changes the text of the button to export without validation and sets the validate_data to false. So on clicking the button the export will start without validation. The buttons actions are changed to be able to switch the with-validation mode. """ self.validate_data = False self.export_tool_button.removeAction( self.set_button_to_export_without_validation_action) self.export_tool_button.removeAction(self.edit_command_action) self.export_tool_button.addAction(self.set_button_to_export_action) self.export_tool_button.addAction(self.edit_command_action) self.export_tool_button.setText(self.export_without_validation_text) def edit_command(self): """ A dialog opens giving the user the possibility to edit the ili2db command used for the export """ exporter = iliexporter.Exporter() exporter.tool = self.type_combo_box.currentData() exporter.configuration = self.updated_configuration() command = exporter.command(True) edit_command_dialog = EditCommandDialog(self) edit_command_dialog.command_edit.setPlainText(command) if edit_command_dialog.exec_(): edited_command = edit_command_dialog.command_edit.toPlainText() self.accepted(edited_command) def accepted(self, edited_command=None): db_id = self.type_combo_box.currentData() res, message = self._lst_panel[db_id].is_valid() if not res: self.txtStdout.setText(message) return configuration = self.updated_configuration() if not edited_command: if (not self.xtf_file_line_edit.validator().validate( configuration.xtffile, 0)[0] == QValidator.Acceptable): self.txtStdout.setText( self. tr("Please set a valid INTERLIS XTF file before exporting data." )) self.xtf_file_line_edit.setFocus() return if not configuration.ilimodels: self.txtStdout.setText( self.tr("Please set a model before exporting data.")) self.export_models_view.setFocus() return # If xtf browser was opened and the file exists, the user already chose # to overwrite the file if (os.path.isfile(self.xtf_file_line_edit.text().strip()) and not self.xtf_browser_was_opened): self.msg = QMessageBox() self.msg.setIcon(QMessageBox.Warning) self.msg.setText( self.tr( "{filename} already exists.\nDo you want to replace it?"). format(filename=os.path.basename( self.xtf_file_line_edit.text().strip()))) self.msg.setWindowTitle(self.tr("Save in XTF Transfer File")) self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box = self.msg.exec_() if msg_box == QMessageBox.No: return with OverrideCursor(Qt.WaitCursor): self.progress_bar.show() self.progress_bar.setValue(0) self.disable() self.txtStdout.setTextColor(QColor(LogColor.COLOR_INFO)) self.txtStdout.clear() exporter = iliexporter.Exporter() exporter.tool = self.type_combo_box.currentData() exporter.configuration = configuration self.save_configuration(configuration) exporter.stdout.connect(self.print_info) exporter.stderr.connect(self.on_stderr) exporter.process_started.connect(self.on_process_started) exporter.process_finished.connect(self.on_process_finished) self.progress_bar.setValue(25) try: if exporter.run( edited_command) != iliexporter.Exporter.SUCCESS: if configuration.db_ili_version == 3: # failed with a db created by ili2db version 3 if not edited_command: # fallback because of issues with --export3 argument self.show_message( Qgis.Warning, self. tr("Tried export with ili2db version 3.x.x (fallback)" ), ) exporter.version = 3 # ... and enforce the Exporter to use ili2db version 3.x.x if exporter.run() != iliexporter.Exporter.SUCCESS: self.enable() self.progress_bar.hide() return else: self.show_message( Qgis.Warning, self. tr("Tried export with ili2db version 3.x.x (no fallback with editted command)" ), ) return else: self.enable() self.progress_bar.hide() return except JavaNotFoundError as e: self.txtStdout.setTextColor(QColor(LogColor.COLOR_INFO)) self.txtStdout.clear() self.txtStdout.setText(e.error_string) self.enable() self.progress_bar.hide() return self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) self.progress_bar.setValue(100) def print_info(self, text): self.txtStdout.setTextColor(QColor(LogColor.COLOR_INFO)) self.txtStdout.append(text) QCoreApplication.processEvents() def on_stderr(self, text): color_log_text(text, self.txtStdout) self.advance_progress_bar_by_text(text) QCoreApplication.processEvents() def show_message(self, level, message): if level == Qgis.Warning: self.bar.pushMessage(message, Qgis.Info, 10) elif level == Qgis.Critical: self.bar.pushMessage(message, Qgis.Warning, 10) def on_process_started(self, command): self.disable() self.txtStdout.setTextColor(QColor(LogColor.COLOR_INFO)) self.txtStdout.clear() self.txtStdout.setText(command) QCoreApplication.processEvents() def on_process_finished(self, exit_code, result): color = "#004905" if exit_code == 0 else "#aa2222" self.txtStdout.setTextColor(QColor(color)) self.txtStdout.append(self.tr("Finished ({})".format(exit_code))) if result == iliexporter.Exporter.SUCCESS: self.buttonBox.clear() self.buttonBox.setEnabled(True) self.buttonBox.addButton(QDialogButtonBox.Close) else: if self.export_without_validate(): self.set_button_to_export_without_validation() self.enable() def export_without_validate(self): """ Valid if an error occurred that prevents executing the export without validations :return: True if you can execute the export without validations, False in other case """ log = self.txtStdout.toPlainText().lower() if "permission denied" in log or "access is denied" in log: return False return True def updated_configuration(self): """ Get the configuration that is updated with the user configuration changes on the dialog. :return: Configuration """ configuration = ili2dbconfig.ExportConfiguration() mode = self.type_combo_box.currentData() self._lst_panel[mode].get_fields(configuration) configuration.tool = mode configuration.xtffile = self.xtf_file_line_edit.text().strip() configuration.ilimodels = ";".join( self.export_models_model.checked_models()) configuration.base_configuration = self.base_configuration configuration.db_ili_version = self.db_ili_version(configuration) if not self.validate_data: configuration.disable_validation = True return configuration def save_configuration(self, configuration): settings = QSettings() settings.setValue("QgisModelBaker/ili2pg/xtffile_export", configuration.xtffile) settings.setValue("QgisModelBaker/importtype", self.type_combo_box.currentData().name) mode = self.type_combo_box.currentData() db_factory = self.db_simple_factory.create_factory(mode) config_manager = db_factory.get_db_command_config_manager( configuration) config_manager.save_config_in_qsettings() def restore_configuration(self): settings = QSettings() for db_id in self.db_simple_factory.get_db_list(False): configuration = iliexporter.ExportConfiguration() db_factory = self.db_simple_factory.create_factory(db_id) config_manager = db_factory.get_db_command_config_manager( configuration) config_manager.load_config_from_qsettings() self._lst_panel[db_id].set_fields(configuration) mode = settings.value("QgisModelBaker/importtype") mode = DbIliMode[ mode] if mode else self.db_simple_factory.default_database mode = mode & ~DbIliMode.ili self.type_combo_box.setCurrentIndex(self.type_combo_box.findData(mode)) self.refresh_db_panel() def disable(self): self.type_combo_box.setEnabled(False) for key, value in self._lst_panel.items(): value.setEnabled(False) self.ili_config.setEnabled(False) self.buttonBox.setEnabled(False) def enable(self): self.type_combo_box.setEnabled(True) for key, value in self._lst_panel.items(): value.setEnabled(True) self.ili_config.setEnabled(True) self.buttonBox.setEnabled(True) def type_changed(self): self.txtStdout.clear() self.set_button_to_export() self.refresh_db_panel() self.refresh_models() self.txtStdout.clear() def refresh_db_panel(self): self.progress_bar.hide() db_id = self.type_combo_box.currentData() self.db_wrapper_group_box.setTitle(displayDbIliMode[db_id]) # Refresh panels for key, value in self._lst_panel.items(): value.interlis_mode = False is_current_panel_selected = db_id == key value.setVisible(is_current_panel_selected) if is_current_panel_selected: value._show_panel() def link_activated(self, link): if link.url() == "#configure": cfg = OptionsDialog(self.base_configuration) if cfg.exec_(): settings = QSettings() settings.beginGroup("QgisModelBaker/ili2db") self.base_configuration.save(settings) else: QDesktopServices.openUrl(link) def help_requested(self): os_language = QLocale( QSettings().value("locale/userLocale")).name()[:2] if os_language in ["es", "de"]: webbrowser.open( "https://opengisch.github.io/QgisModelBaker/docs/{}/user-guide.html#export-an-interlis-transfer-file-xtf" .format(os_language)) else: webbrowser.open( "https://opengisch.github.io/QgisModelBaker/docs/user-guide.html#export-an-interlis-transfer-file-xtf" ) def xtf_browser_opened_to_true(self): """ Slot. Sets a flag to true to eventually avoid asking a user whether to overwrite a file. """ self.xtf_browser_was_opened = True def xtf_browser_opened_to_false(self): """ Slot. Sets a flag to false to eventually ask a user whether to overwrite a file. """ self.xtf_browser_was_opened = False def advance_progress_bar_by_text(self, text): if text.strip() == "Info: compile models…": self.progress_bar.setValue(50) elif text.strip() == "Info: create table structure…": self.progress_bar.setValue(75)
class DatasetManagerDialog(QDialog, DIALOG_UI): def __init__(self, iface, parent=None, wizard_embedded=False): QDialog.__init__(self, parent) self.iface = iface self._close_editing() self.setupUi(self) self.buttonBox.accepted.connect(self._accepted) self.buttonBox.rejected.connect(self._rejected) self.type_combo_box.clear() self._lst_panel = dict() self.db_simple_factory = DbSimpleFactory() for db_id in self.db_simple_factory.get_db_list(False): self.type_combo_box.addItem(displayDbIliMode[db_id], db_id) db_factory = self.db_simple_factory.create_factory(db_id) item_panel = db_factory.get_config_panel(self, DbActionType.EXPORT) self._lst_panel[db_id] = item_panel self.db_layout.addWidget(item_panel) self.type_combo_box.currentIndexChanged.connect(self._type_changed) # when opened by the wizard it uses the current db connection settings and should not be changable self.db_frame.setHidden(wizard_embedded) self.dataset_model = DatasetModel() self.dataset_tableview.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.dataset_tableview.horizontalHeader().hide() self.dataset_tableview.verticalHeader().hide() self.dataset_tableview.setSelectionMode(QTableView.SingleSelection) self.dataset_tableview.setModel(self.dataset_model) self._restore_configuration() # refresh the models on changing values but avoid massive db connects by timer self.refreshTimer = QTimer() self.refreshTimer.setSingleShot(True) self.refreshTimer.timeout.connect( lambda: self._refresh_datasets(self._updated_configuration())) for key, value in self._lst_panel.items(): value.notify_fields_modified.connect( self._request_for_refresh_datasets) self._refresh_datasets(self._updated_configuration()) self.add_button.clicked.connect(self._add_dataset) self.edit_button.clicked.connect(self._edit_dataset) self.create_baskets_button.clicked.connect(self._create_baskets) self.dataset_tableview.selectionModel().selectionChanged.connect( lambda: self._enable_dataset_handling(True)) self.add_button.setIcon( QgsApplication.getThemeIcon("/symbologyAdd.svg")) self.edit_button.setIcon( QgsApplication.getThemeIcon("/symbologyEdit.svg")) def _close_editing(self): editable_layers = [] for layer in QgsProject.instance().mapLayers().values(): if layer.type() == QgsMapLayer.VectorLayer: self.iface.vectorLayerTools().stopEditing(layer) if layer.isEditable(): editable_layers.append(layer) if editable_layers: warning_box = QMessageBox(self) warning_box.setIcon(QMessageBox.Warning) warning_title = self.tr("Layers still editable") warning_box.setWindowTitle(warning_title) warning_box.setText( self. tr("You still have layers in edit mode.\nIn case you modify datasets on the database of those layers, it could lead to database locks.\nEditable layers are:\n - {}" ).format("\n - ".join( [layer.name() for layer in editable_layers]))) warning_box.exec_() def _valid_selection(self): """ Returns if at least one dataset is selected """ return bool(self.dataset_tableview.selectedIndexes()) def _enable_dataset_handling(self, enable): self.dataset_tableview.setEnabled(enable) self.add_button.setEnabled(enable) self.edit_button.setEnabled(self._valid_selection()) self.create_baskets_button.setEnabled(self._valid_selection()) def _type_changed(self): ili_mode = self.type_combo_box.currentData() db_id = ili_mode & ~DbIliMode.ili self.db_wrapper_group_box.setTitle(displayDbIliMode[db_id]) # Refresh panels for key, value in self._lst_panel.items(): is_current_panel_selected = db_id == key value.setVisible(is_current_panel_selected) if is_current_panel_selected: value._show_panel() self._refresh_datasets(self._updated_configuration()) def _request_for_refresh_datasets(self): # hold refresh back self.refreshTimer.start(500) def _refresh_datasets(self, configuration): db_connector = db_utils.get_db_connector(configuration) if db_connector and db_connector.get_basket_handling: self._enable_dataset_handling(True) return self.dataset_model.refresh_model(db_connector) else: self._enable_dataset_handling(False) return self.dataset_model.clear() def _add_dataset(self): db_connector = db_utils.get_db_connector(self._updated_configuration()) if db_connector and db_connector.get_basket_handling: edit_dataset_dialog = EditDatasetDialog(self, db_connector) edit_dataset_dialog.exec_() self._refresh_datasets(self._updated_configuration()) self._jump_to_entry(edit_dataset_dialog.dataset_line_edit.text()) def _edit_dataset(self): if self._valid_selection(): db_connector = db_utils.get_db_connector( self._updated_configuration()) if db_connector and db_connector.get_basket_handling: dataset = ( self.dataset_tableview.selectedIndexes()[0].data( int(DatasetModel.Roles.TID)), self.dataset_tableview.selectedIndexes()[0].data( int(DatasetModel.Roles.DATASETNAME)), ) edit_dataset_dialog = EditDatasetDialog( self, db_connector, dataset) edit_dataset_dialog.exec_() self._refresh_datasets(self._updated_configuration()) self._jump_to_entry( edit_dataset_dialog.dataset_line_edit.text()) def _create_baskets(self): if self._valid_selection(): db_connector = db_utils.get_db_connector( self._updated_configuration()) if db_connector and db_connector.get_basket_handling: feedbacks = [] for record in db_connector.get_topics_info(): dataset_tid = self.dataset_tableview.selectedIndexes( )[0].data(int(DatasetModel.Roles.TID)) status, message = db_connector.create_basket( dataset_tid, ".".join([record["model"], record["topic"]])) feedbacks.append((status, message)) info_box = QMessageBox(self) info_box.setIcon(QMessageBox.Warning if len( [feedback for feedback in feedbacks if not feedback[0]]) else QMessageBox.Information) info_title = self.tr("Created baskets") info_box.setWindowTitle(info_title) info_box.setText("{}{}".format( "\n".join([feedback[1] for feedback in feedbacks]), "\n\nBe aware that the IDs of the baskets are created as UUIDs. To change that, edit the t_ili2db_basket table manually." if len([feedback for feedback in feedbacks if feedback[0]]) else "", )) info_box.exec_() def _jump_to_entry(self, datasetname): matches = self.dataset_model.match( self.dataset_model.index(0, 0), Qt.DisplayRole, datasetname, 1, Qt.MatchExactly, ) if matches: self.dataset_tableview.setCurrentIndex(matches[0]) self.dataset_tableview.scrollTo(matches[0]) def _restore_configuration(self): settings = QSettings() for db_id in self.db_simple_factory.get_db_list(False): configuration = Ili2DbCommandConfiguration() db_factory = self.db_simple_factory.create_factory(db_id) config_manager = db_factory.get_db_command_config_manager( configuration) config_manager.load_config_from_qsettings() self._lst_panel[db_id].set_fields(configuration) mode = settings.value("QgisModelBaker/importtype") mode = DbIliMode[ mode] if mode else self.db_simple_factory.default_database mode = mode & ~DbIliMode.ili self.type_combo_box.setCurrentIndex(self.type_combo_box.findData(mode)) self._type_changed() def _updated_configuration(self): configuration = Ili2DbCommandConfiguration() mode = self.type_combo_box.currentData() self._lst_panel[mode].get_fields(configuration) configuration.tool = mode configuration.db_ili_version = db_utils.db_ili_version(configuration) return configuration def _save_configuration(self, configuration): settings = QSettings() settings.setValue("QgisModelBaker/importtype", self.type_combo_box.currentData().name) mode = self.type_combo_box.currentData() db_factory = self.db_simple_factory.create_factory(mode) config_manager = db_factory.get_db_command_config_manager( configuration) config_manager.save_config_in_qsettings() def _accepted(self): self._save_configuration(self._updated_configuration()) self.close() def _rejected(self): self._restore_configuration() self.close()