class GeoServerWidget(ServerWidgetBase, BASE, WIDGET): def __init__(self, parent, server_type): super().__init__(parent, server_type) self.setupUi(self) self.geoserverAuth = QgsAuthConfigSelect() self.geoserverAuth.selectedConfigIdChanged.connect(self.setDirty) self.addAuthWidget() self.populateStorageCombo() self.comboStorageType.currentIndexChanged.connect(self.datastoreChanged) self.btnRefreshDatabases.clicked.connect(partial(self.updateDbServersCombo, True)) self.btnAddDatastore.clicked.connect(self.addPostgisDatastore) self.txtGeoserverName.textChanged.connect(self.setDirty) self.txtGeoserverUrl.textChanged.connect(self.setDirty) self.chkUseOriginalDataSource.stateChanged.connect(self.setDirty) self.chkUseVectorTiles.stateChanged.connect(self.setDirty) self.comboGeoserverDatabase.currentIndexChanged.connect(self.setDirty) # Declare progress dialog self._pgdialog = None def createServerInstance(self): """ Reads the settings form fields and returns a new server instance with these settings. """ db = None storage = self.comboStorageType.currentIndex() if storage in (GeoserverStorage.POSTGIS_BRIDGE, GeoserverStorage.POSTGIS_GEOSERVER): db = self.comboGeoserverDatabase.currentText() try: name = self.txtGeoserverName.text().strip() url = self.txtGeoserverUrl.text().strip() if not name: raise RuntimeError(f'missing {self.serverType.getLabel()} name') if not url: raise RuntimeError(f'missing {self.serverType.getLabel()} URL') return self.serverType( name=name, authid=self.geoserverAuth.configId() or None, url=url, storage=storage, postgisdb=db, useOriginalDataSource=self.chkUseOriginalDataSource.isChecked(), useVectorTiles=self.chkUseVectorTiles.isChecked() ) except Exception as e: self.parent.logError(f"Failed to create {self.serverType.getLabel()} instance: {e}") return None def newFromName(self, name: str): """ Sets the name field and keeps all others empty. """ self.txtGeoserverName.setText(name) self.txtGeoserverUrl.clear() self.geoserverAuth.setConfigId(None) # Set datastore and database comboboxes self.comboStorageType.blockSignals(True) self.comboStorageType.setCurrentIndex(GeoserverStorage.FILE_BASED) self.datastoreChanged(GeoserverStorage.FILE_BASED) self.chkUseOriginalDataSource.setChecked(False) self.chkUseVectorTiles.setChecked(False) self.comboStorageType.blockSignals(False) def loadFromInstance(self, server): """ Populates the form fields with the values from the given server instance. """ self.txtGeoserverName.setText(server.serverName) self.txtGeoserverUrl.setText(server.baseUrl) self.geoserverAuth.setConfigId(server.authId) # Set datastore and database comboboxes self.comboStorageType.blockSignals(True) self.comboStorageType.setCurrentIndex(server.storage) self.datastoreChanged(server.storage, server.postgisdb) self.chkUseOriginalDataSource.setChecked(server.useOriginalDataSource) self.chkUseVectorTiles.setChecked(server.useVectorTiles) self.comboStorageType.blockSignals(False) # After the data has loaded, the form is "clean" self.setClean() def addAuthWidget(self): layout = QHBoxLayout() layout.setContentsMargins(0, 3, 0, 0) layout.addWidget(self.geoserverAuth) self.geoserverAuthWidget.setLayout(layout) self.geoserverAuthWidget.setFixedHeight(self.txtGeoserverUrl.height()) def populateStorageCombo(self): self.comboStorageType.clear() self.comboStorageType.addItems(GeoserverStorage.values()) def datastoreChanged(self, storage, init_value=None): """ Called each time the database combobox selection changed. """ if storage is None: storage = GeoserverStorage[self.comboStorageType.currentIndex()] if storage == GeoserverStorage.POSTGIS_BRIDGE: self.updateDbServersCombo(False, init_value) self.comboGeoserverDatabase.setVisible(True) self.labelGeoserverDatastore.setText('Database') self.labelGeoserverDatastore.setVisible(True) self.datastoreControls.setVisible(False) elif storage == GeoserverStorage.POSTGIS_GEOSERVER: self.comboGeoserverDatabase.setVisible(True) self.labelGeoserverDatastore.setText('Datastore') self.labelGeoserverDatastore.setVisible(True) self.datastoreControls.setVisible(True) self.updateDbServersCombo(True, init_value) elif storage == GeoserverStorage.FILE_BASED: self.comboGeoserverDatabase.setVisible(False) self.labelGeoserverDatastore.setVisible(False) self.datastoreControls.setVisible(False) self.setDirty() def addGeoserverPgDatastores(self, current, result): if self._pgdialog and self._pgdialog.isVisible(): self._pgdialog.hide() if result: # Worker result might be a list of lists, so we should flatten it datastores = list(chain.from_iterable(result)) self.comboGeoserverDatabase.addItems(datastores) if current: self.comboGeoserverDatabase.setCurrentText(current) else: self.parent.showWarningBar("Warning", "No PostGIS datastores on server or could not retrieve them") def showProgressDialog(self, text, length, handler): self._pgdialog = QProgressDialog(text, "Cancel", 0, length, self) self._pgdialog.setWindowTitle(getAppName()) self._pgdialog.setWindowModality(QtCore.Qt.WindowModal) self._pgdialog.canceled.connect(handler, type=QtCore.Qt.DirectConnection) self._pgdialog.forceShow() def updateDbServersCombo(self, managed_by_geoserver: bool, init_value=None): """ (Re)populate the combobox with database-driven datastores. :param managed_by_geoserver: If True, GeoServer manages the DB connection. If False, Bridge manages it. :param init_value: When the combobox shows for the first time and no databases have been loaded, this value can be set immediately as the only available and selected item. Doing so prevents a full refresh of GeoServer datastores. """ if managed_by_geoserver and init_value: if self.comboGeoserverDatabase.count() == 0: # Only add the given init_value item to the empty combo (user should manually refresh) self.comboGeoserverDatabase.addItem(init_value) self.comboGeoserverDatabase.setCurrentText(init_value) return # If combo has values, try and find the init_value and set it to that (user should manually refresh) index = self.comboGeoserverDatabase.findText(init_value) if index >= 0: self.comboGeoserverDatabase.setCurrentIndex(index) return current_db = self.comboGeoserverDatabase.currentText() or init_value self.comboGeoserverDatabase.clear() if managed_by_geoserver: # Database is managed by GeoServer: instantiate server and retrieve datastores # TODO: only PostGIS datastores are supported for now server = self.createServerInstance() if not server: self.parent.showErrorBar("Error", "Bad values in server definition") return try: # Retrieve workspaces (single REST request) workspaces = gui.execute(server.getWorkspaces) if not workspaces: return # Retrieve datastores for each workspace: # This is a potentially long-running operation and uses a separate QThread worker = gui.ItemProcessor(workspaces, server.getPostgisDatastores) self.showProgressDialog("Fetching PostGIS datastores...", len(workspaces), worker.stop) worker.progress.connect(self._pgdialog.setValue) worker.finished.connect(partial(self.addGeoserverPgDatastores, current_db)) worker.run() except Exception as e: msg = f'Failed to retrieve datastores for {self.serverName}' if isinstance(e, HTTPError) and e.response.status_code == 401: msg = f'{msg}: please check credentials' else: msg = f'{msg}: {e}' self.parent.showErrorBar(msg) else: # Database is managed by Bridge: iterate over all user-defined database connections db_servers = self.parent.serverManager.getDbServerNames() self.comboGeoserverDatabase.addItems(db_servers) if current_db in db_servers: self.comboGeoserverDatabase.setCurrentText(current_db) def addPostgisDatastore(self): server = self.createServerInstance() if server is None: self.parent.showErrorBar("Error", "Wrong values in server definition") return dlg = GeoserverDatastoreDialog(self) dlg.exec_() name = dlg.name if name is None: return def _entry(k, v): return {"@key": k, "$": v} ds = { "dataStore": { "name": dlg.name, "type": "PostGIS", "enabled": True, "connectionParameters": { "entry": [ _entry("schema", dlg.schema), _entry("port", dlg.port), _entry("database", dlg.database), _entry("passwd", dlg.password), _entry("user", dlg.username), _entry("host", dlg.host), _entry("dbtype", "postgis") ] } } } try: gui.execute(partial(server.addPostgisDatastore, ds)) except Exception as e: self.parent.showErrorBar("Error", "Could not create new PostGIS dataset", propagate=e) else: self.updateDbServersCombo(True)
def isochrone( database_name, host_name, port_number, user_name, password, network, network_geom, network_id_column, catchment, catchment_geom, catchment_id_column, style_checked, contour_interval, parent_dialog, progress_dialog=None): """Contains main logic on creating isochrone map :param database_name: Database name :type database_name: str :param host_name: Database host :type host_name: str :param port_number: Port number for the host :type port_number: str :param user_name: Username for connection with database :type user_name: str :param password: Password :type password: str :param network: Schema and Table containing the network. :type network: str :param network_geom: Geometry column in network. :type network_geom: str :param network_id_column: Id column in network. :type network_id_column: str :param catchment: Schema and Table containing catchment areas. :type catchment: str :param catchment_geom: Geometry column in catchment. :type catchment_geom: str :param catchment_id_column: Id column in catchment. :type catchment_id_column: str :param style_checked: Value for either to create a map style or not. :type style_checked: boolean :param contour_interval: Interval between contour, if contour will be generated :type contour_interval: int :param parent_dialog: A dialog that called this function. :type parent_dialog: QProgressDialog :param progress_dialog: A progess dialog . :type progress_dialog: QProgressDialog :returns layer_name: temporary path of the isochrones map layer :rtype layer_name: str """ # Import files into database, have tables # connect to database # add the files get the tables name connection = psycopg2.connect( "dbname='" + str(database_name) + "' " "user='******' " "host='" + str(host_name) + "' " "port='" + str(port_number) + "' " "password='******' ") curr = connection.cursor() # Setting up progress window progress_dialog = QProgressDialog('Progress', 'Cancel', 0, 100) progress_dialog.forceShow() progress_dialog.setWindowModality(Qt.WindowModal) network_array = network.split('.') network_table = str(network_array[1]) network_schema = network_array[0] catchment = catchment.split('.') catchment_table = catchment[1] catchment_schema = catchment[0] if not network_geom: network_geom = "geom" if not catchment_geom: catchment_geom = "geom" arguments = {} arguments["network_table"] = network_table arguments["network_geom"] = network_geom arguments["network_id"] = network_id_column arguments["catchment_table"] = catchment_table arguments["catchment_geom"] = catchment_geom arguments["catchment_id"] = catchment_id_column arguments["database_name"] = database_name arguments["port_number"] = port_number try: create_network_view( connection, curr, arguments, parent_dialog, progress_dialog) create_nodes(connection, curr, arguments, parent_dialog) # Create routable network progress_percentage = 10 progress_dialog.setValue(progress_percentage) label_text = tr("Creating a routable network table") progress_dialog.setLabelText(label_text) if progress_dialog.wasCanceled(): return create_routable_network(connection, curr, arguments, parent_dialog) # Find nearest nodes from the catchments progress_percentage = 30 progress_dialog.setValue(progress_percentage) label_text = tr("Preparing the catchment table") progress_dialog.setLabelText(label_text) if progress_dialog.wasCanceled(): return update_catchment(connection, curr, arguments, parent_dialog) # Calculate drivetimes for the nearest nodes progress_percentage = 50 progress_dialog.setValue(progress_percentage) label_text = tr("Calculating drivetime for each catchment area") progress_dialog.setLabelText(label_text) if progress_dialog.wasCanceled(): return progress_percentage = calculate_drivetimes( connection, curr, arguments, progress_dialog, parent_dialog, progress_percentage) if progress_dialog.wasCanceled(): return prepare_drivetimes_table(connection, curr, arguments, parent_dialog) uri = QgsDataSourceUri() # set host name, port, database name, username and password uri.setConnection( host_name, port_number, database_name, user_name, password) # set database schema, table name, geometry column and optionally # subset (WHERE clause) uri.setDataSource( network_schema, "catchment_final_no_null", "the_geom") # Export table as a shapefile layer = QgsVectorLayer(uri.uri(), "isochrones", "ogr") temp_layer = QgsVectorLayer(uri.uri(), "isochrones", "postgres") QgsProject.instance().addMapLayers([temp_layer]) if iface: iface.mapCanvas().refresh() layer_name = temp_layer.dataProvider().dataSourceUri() if style_checked: args = {} args['network_schema'] = network_schema args['network_table'] = network_table args['network_geom'] = network_geom args['catchment_schema'] = catchment_schema args['catchment_table'] = catchment_table args['catchment_geom'] = catchment_geom prepare_map_style( uri, progress_percentage, contour_interval, progress_dialog, temp_layer, parent_dialog, args) if progress_dialog: progress_dialog.setValue(100) progress_dialog.done(QDialog.Accepted) return layer_name except (IsochroneDBError, IsochroneMapStyleError ) as exception: return None