def getSnapper(self): """ Getter for the snapper """ self.snapper = QgsSnapper(self.iface.mapCanvas().mapRenderer()) snap_layer = QgsSnapper.SnapLayer() snap_layer.mLayer = self.nodeLayer snap_layer.mTolerance = 10 snap_layer.mUnitType = QgsTolerance.Pixels snap_layer.mSnapTo = QgsSnapper.SnapToVertex self.snapper.setSnapLayers([snap_layer]) return self.snapper
def snap(self,pos): snapper = QgsSnapper( self.iface.mapCanvas().mapSettings() ) snapNodeLayer = QgsSnapper.SnapLayer() snapNodeLayer.mLayer = self.nodeLayer snapNodeLayer.mTolerance = 10 snapNodeLayer.mUnitType = QgsTolerance.Pixels snapNodeLayer.mSnapTo = QgsSnapper.SnapToVertex snapper.setSnapLayers([snapNodeLayer]) (_, snappedPoints) = snapper.snapPoint(pos) if len(snappedPoints) >= 1: self.currentSnappingResult = snappedPoints[0] return self.currentSnappingResult.snappedVertex else: snapper = QgsSnapper( self.iface.mapCanvas().mapSettings() ) snapReachLayer = QgsSnapper.SnapLayer() snapReachLayer.mLayer = self.reachLayer snapReachLayer.mTolerance = 10 snapReachLayer.mUnitType = QgsTolerance.Pixels snapReachLayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment snapper.setSnapLayers([snapReachLayer]) (_, snappedPoints) = snapper.snapPoint(pos) if len(snappedPoints) >= 1: self.currentSnappingResult = snappedPoints[0] return self.currentSnappingResult.snappedVertex else: self.currentSnappingResult = None return pos
def snap(self, pos): """ Snap to nearby points on the wastewater node layer which may be used as connection points for this reach. :param pos: The position to snap :return: The snapped position """ snapper = QgsSnapper(self.iface.mapCanvas().mapSettings()) snap_nodelayer = QgsSnapper.SnapLayer() snap_nodelayer.mLayer = self.nodeLayer snap_nodelayer.mTolerance = 10 snap_nodelayer.mUnitType = QgsTolerance.Pixels snap_nodelayer.mSnapTo = QgsSnapper.SnapToVertex snapper.setSnapLayers([snap_nodelayer]) (_, snappedPoints) = snapper.snapPoint(pos) if len(snappedPoints) >= 1: self.currentSnappingResult = snappedPoints[0] return self.currentSnappingResult.snappedVertex else: snapper = QgsSnapper(self.iface.mapCanvas().mapSettings()) snap_reachlayer = QgsSnapper.SnapLayer() snap_reachlayer.mLayer = self.reachLayer snap_reachlayer.mTolerance = 10 snap_reachlayer.mUnitType = QgsTolerance.Pixels snap_reachlayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment snapper.setSnapLayers([snap_reachlayer]) (_, snappedPoints) = snapper.snapPoint(pos) if len(snappedPoints) >= 1: self.currentSnappingResult = snappedPoints[0] return self.currentSnappingResult.snappedVertex else: self.currentSnappingResult = None return pos
def snapPoint(self, event): pClicked = QPoint( event.pos().x(), event.pos().y() ) self.snapper = QgsSnapper( self.iface.mapCanvas().mapRenderer() ) snapLayer = QgsSnapper.SnapLayer() snapLayer.mLayer = self.nodeLayer snapLayer.mTolerance = 10 snapLayer.mUnitType = QgsTolerance.Pixels snapLayer.mSnapTo = QgsSnapper.SnapToVertex self.snapper.setSnapLayers( [snapLayer] ) ( _, snappedPoints ) = self.snapper.snapPoint( pClicked, [] ) if len( snappedPoints ) == 0: return None elif len( snappedPoints ) == 1: return snappedPoints[0] elif len( snappedPoints ) > 1: attrObjId = self.getNodeLayer().dataProvider().fieldNameIndex('obj_id') attrType = self.getNodeLayer().dataProvider().fieldNameIndex('type') attrDescr = self.getNodeLayer().dataProvider().fieldNameIndex('description') attributes = [attrObjId, attrType, attrDescr ] pointIds = [point.snappedAtGeometry for point in snappedPoints] nodeFeatures = self.getFeaturesById(self.getNodeLayer(), attributes, pointIds, True) # Filter wastewater nodes filteredFeatures = { id: nodeFeatures.featureById(id) for id in nodeFeatures.asDict() if nodeFeatures.attrAsUnicode( nodeFeatures.featureById(id), u'type' ) == u'wastewater_node' } # Only one wastewater node left: return this if len( filteredFeatures ) == 1: return [point for point in snappedPoints if point.snappedAtGeometry == filteredFeatures.iterkeys().next() ][0] # Still not sure which point to take? # Are there no wastewater nodes filtered? Let the user choose from the reach points if len( filteredFeatures ) == 0: filteredFeatures = nodeFeatures.asDict() # Ask the user which point he wants to use actions = dict() menu = QMenu( self.iface.mapCanvas() ) for id, feature in filteredFeatures.iteritems(): try: title = feature.attribute( 'description' ) + " (" + feature.attribute( 'obj_id' ) + ")" except TypeError: title = " (" + feature.attribute( 'obj_id' ) + ")" action = QAction( title, menu ) actions[action] = point menu.addAction( action ) clickedAction = menu.exec_( self.iface.mapCanvas().mapToGlobal( event.pos() ) ) if clickedAction is not None: return actions[clickedAction] return None
def snapToLayers(self, pixPoint, initPoint=None): self.snapping = self.settings.value("obsDistanceSnapping") if self.snapping == "no": return initPoint if self.snapping == "project": ok, snappingResults = QgsMapCanvasSnapper(self.mapCanvas).snapToBackgroundLayers(pixPoint, []) self.displaySnapInfo(snappingResults) if ok == 0 and len(snappingResults) > 0: return QgsPoint(snappingResults[0].snappedVertex) else: return initPoint if self.snapping == "all": if len(self.snapperList) == 0: return initPoint snapper = QgsSnapper(self.mapCanvas.mapRenderer()) snapper.setSnapLayers(self.snapperList) snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances) ok, snappingResults = snapper.snapPoint(pixPoint, []) self.displaySnapInfo(snappingResults) if ok == 0 and len(snappingResults) > 0: return QgsPoint(snappingResults[0].snappedVertex) else: return initPoint
def set_up_snap_layer(vlayer, tolerance=None, snapping_type=QgsSnapper.SnapToVertex): snap_layer = QgsSnapper.SnapLayer() snap_layer.mLayer = vlayer if tolerance is None or tolerance < 0: (a, b, c, d, tolerance, f) = QgsProject.instance().snapSettingsForLayer(vlayer.id()) snap_layer.mTolerance = tolerance else: snap_layer.mTolerance = tolerance snap_layer.mUnitType = QgsTolerance.MapUnits snap_layer.mSnapTo = snapping_type return snap_layer
def activate(self): QgsMapTool.activate(self) self.rubber.setWidth(self.settings.value("rubberWidth")) self.rubber.setColor(self.settings.value("rubberColor")) lineLayer = MemoryLayers(self.iface).lineLayer() # unset this tool if the layer is removed lineLayer.layerDeleted.connect(self.unsetMapTool) self.layerId = lineLayer.id() # create snapper for this layer self.snapLayer = QgsSnapper.SnapLayer() self.snapLayer.mLayer = lineLayer self.snapLayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment self.snapLayer.mTolerance = self.settings.value("selectTolerance") if self.settings.value("selectUnits") == "map": self.snapLayer.mUnitType = QgsTolerance.MapUnits else: self.snapLayer.mUnitType = QgsTolerance.Pixels
def getFeatures(self, pixPoint): snapper = QgsSnapper(self.mapCanvas.mapRenderer()) snapper.setSnapLayers([self.snapLayer]) snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances) ok, snappingResults = snapper.snapPoint(pixPoint, []) # output snapped features features = [] alreadyGot = [] for result in snappingResults: featureId = result.snappedAtGeometry f = QgsFeature() if featureId not in alreadyGot: if result.layer.getFeatures(QgsFeatureRequest().setFilterFid( featureId)).nextFeature(f) is not False: features.append(QgsFeature(f)) alreadyGot.append(featureId) return features
def snapToDimensionLayer(self, pixPoint): snapper = QgsSnapper(self.mapCanvas.mapRenderer()) snapper.setSnapLayers([self.snapLayer]) snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances) ok, snappingResults = snapper.snapPoint(pixPoint, []) for result in snappingResults: featureId = result.snappedAtGeometry f = QgsFeature() if self.snapLayer.mLayer.getFeatures(QgsFeatureRequest().setFilterFid(featureId)).nextFeature(f) is not False: if self.observationType == "orientation": line = f.geometry().asPolyline() if len(line) != 2: continue return f return None
def getFeatures(self, pixPoint): snapper = QgsSnapper(self.mapCanvas.mapRenderer()) snapper.setSnapLayers([self.snapLayer]) snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances) ok, snappingResults = snapper.snapPoint(pixPoint, []) # output snapped features features = [] alreadyGot = [] for result in snappingResults: featureId = result.snappedAtGeometry f = QgsFeature() if featureId not in alreadyGot: if result.layer.getFeatures(QgsFeatureRequest().setFilterFid(featureId)).nextFeature(f) is not False: features.append(QgsFeature(f)) alreadyGot.append(featureId) return features
def getOrientation(self, pixPoint): snapperList = [] scale = self.iface.mapCanvas().mapRenderer().scale() for layer in self.iface.mapCanvas().layers(): if layer.type() == QgsMapLayer.VectorLayer and layer.hasGeometryType(): if layer.geometryType() in (QGis.Line, QGis.Polygon): if not layer.hasScaleBasedVisibility() or layer.minimumScale() < scale <= layer.maximumScale(): snapLayer = QgsSnapper.SnapLayer() snapLayer.mLayer = layer snapLayer.mSnapTo = QgsSnapper.SnapToSegment snapLayer.mTolerance = self.settings.value("selectTolerance") if self.settings.value("selectUnits") == "map": snapLayer.mUnitType = QgsTolerance.MapUnits else: snapLayer.mUnitType = QgsTolerance.Pixels snapperList.append(snapLayer) if len(snapperList) == 0: return None snapper = QgsSnapper(self.canvas.mapRenderer()) snapper.setSnapLayers(snapperList) snapper.setSnapMode(QgsSnapper.SnapWithOneResult) f = QgsFeature() ok, snappingResults = snapper.snapPoint(pixPoint, []) if ok == 0: for result in snappingResults: if result.layer.getFeatures(QgsFeatureRequest().setFilterFid(result.snappedAtGeometry)).nextFeature(f) is False: continue if not isFeatureRendered(self.canvas, result.layer, f): continue vertices = (result.afterVertex, result.beforeVertex) po = result.snappedVertex dist = (po.sqrDist(vertices[0]), po.sqrDist(vertices[1])) mindist = min(dist) if mindist == 0: return None i = dist.index(mindist) ve = vertices[i] az = po.azimuth(ve) return Orientation(self.iface, ve, az) else: return None
def getFeatures(self, pixPoint): # do the snapping snapper = QgsSnapper(self.mapCanvas.mapRenderer()) snapper.setSnapLayers(self.snapperList) snapper.setSnapMode(QgsSnapper.SnapWithResultsWithinTolerances) ok, snappingResults = snapper.snapPoint(pixPoint, []) # output snapped features features = [] alreadyGot = [] for result in snappingResults: featureId = result.snappedAtGeometry f = QgsFeature() if (result.layer.id(), featureId) not in alreadyGot: if result.layer.getFeatures(QgsFeatureRequest().setFilterFid(featureId)).nextFeature(f) is False: continue if not isFeatureRendered(self.mapCanvas, result.layer, f): continue features.append(QgsFeature(f)) features[-1].layer = result.layer alreadyGot.append((result.layer.id(), featureId)) return features
def snap(self, pos): """ Snap to nearby points on the wastewater node layer which may be used as connection points for this reach. :param pos: The position to snap :return: The snapped position """ snapper = QgsSnapper(self.iface.mapCanvas().mapSettings()) snap_nodelayer = QgsSnapper.SnapLayer() snap_nodelayer.mLayer = self.nodeLayer snap_nodelayer.mTolerance = 10 snap_nodelayer.mUnitType = QgsTolerance.Pixels snap_nodelayer.mSnapTo = QgsSnapper.SnapToVertex snapper.setSnapLayers([snap_nodelayer]) (_, snappedPoints) = snapper.snapPoint(pos) if snappedPoints: self.currentSnappingResult = snappedPoints[0] return self.currentSnappingResult.snappedVertex else: snapper = QgsSnapper(self.iface.mapCanvas().mapSettings()) snap_reachlayer = QgsSnapper.SnapLayer() snap_reachlayer.mLayer = self.reachLayer snap_reachlayer.mTolerance = 10 snap_reachlayer.mUnitType = QgsTolerance.Pixels snap_reachlayer.mSnapTo = QgsSnapper.SnapToVertexAndSegment snapper.setSnapLayers([snap_reachlayer]) (_, snappedPoints) = snapper.snapPoint(pos) if snappedPoints: self.currentSnappingResult = snappedPoints[0] return self.currentSnappingResult.snappedVertex else: self.currentSnappingResult = None return pos
class QgepGraphManager(QObject): """ Manages a graph """ reachLayer = None reachLayerId = -1 nodeLayer = None nodeLayerId = -1 dirty = True graph = None vertexIds = {} nodesOnStructure = defaultdict(list) # Logs performance of graph creation timings = [] def __init__(self, iface): QObject.__init__(self) self.iface = iface self.snapper = None def setReachLayer(self, reach_layer): """ Set the reach layer (edges) """ self.reachLayer = reach_layer self.dirty = True if reach_layer: self.reachLayerId = reach_layer.id() else: self.reachLayerId = 0 if self.nodeLayer and self.reachLayer: self.createGraph() def setNodeLayer(self, node_layer): """ Set the node layer """ self.dirty = True self.nodeLayer = node_layer if node_layer: self.nodeLayerId = node_layer.id() else: self.nodeLayerId = 0 if self.nodeLayer and self.reachLayer: self.createGraph() def _addVertices(self): """ Initializes the graph with the vertices from the node layer """ node_provider = self.nodeLayer.dataProvider() features = node_provider.getFeatures() # Add all vertices for feat in features: fid = feat.id() obj_id = feat['obj_id'] obj_type = feat['type'] try: vertex = feat.geometry().asPoint() except AttributeError: # TODO Add to problem log pass self.graph.add_node(fid, dict(point=vertex, objType=obj_type)) self.vertexIds[unicode(obj_id)] = fid self._profile("add vertices") def _addEdges(self): """ Initializes the graph with the edges """ # Add all edges (reach) reach_provider = self.reachLayer.dataProvider() features = reach_provider.getFeatures() # Loop through all reaches for feat in features: try: obj_id = feat['obj_id'] obj_type = feat['type'] from_obj_id = feat['from_obj_id'] to_obj_id = feat['to_obj_id'] length = feat['length_calc'] pt_id1 = self.vertexIds[from_obj_id] pt_id2 = self.vertexIds[to_obj_id] props = { 'weight': length, 'feature': feat.id(), 'baseFeature': obj_id, 'objType': obj_type } self.graph.add_edge(pt_id1, pt_id2, props) except KeyError as e: print e self._profile("add edges") def refresh(self): """ Refreshes the network graph. It will force a refresh of the materialized views in the database and then reload and recreate the graph. """ uri = QgsDataSourceURI(self.nodeLayer.dataProvider().dataSourceUri()) db = QSqlDatabase.addDatabase("QPSQL") # Name of the driver -- doesn't change str_connect_option = "requiressl=0;service=" + uri.service() db.setConnectOptions(str_connect_option) if not db.open(): self.iface.messageBar().pushMessage(self.tr("Warning"), db.lastError().text(), level=QgsMessageBar.CRITICAL) query_template = "REFRESH MATERIALIZED VIEW qgep.vw_network_segment;" query = QSqlQuery(db) if not query.exec_(query_template): str_result = query.lastError().text() self.iface.messageBar().pushMessage(self.tr("Warning"), str_result, level=QgsMessageBar.CRITICAL) else: self.iface.messageBar().pushMessage(self.tr("Success"), "vw_network_segment successfully updated", level=QgsMessageBar.SUCCESS, duration=2) query_template = "REFRESH MATERIALIZED VIEW qgep.vw_network_node;" query = QSqlQuery(db) if not query.exec_(query_template): str_result = query.lastError().text() self.iface.messageBar().pushMessage(self.tr("Warning"), str_result, level=QgsMessageBar.CRITICAL) else: self.iface.messageBar().pushMessage(self.tr("Success"), "vw_network_node successfully updated", level=QgsMessageBar.SUCCESS, duration=2) # recreate networkx graph self.graph.clear() self.createGraph() def _profile(self, name): """ Adds a performance profile snapshot with the given name """ spenttime = 0 if self.timings: spenttime = time.clock() - self.timings[-1][1] self.timings.append((name, spenttime)) # Creates a network graph def createGraph(self): """ Create a graph """ self._profile("create graph") # try: self.vertexIds = {} self.nodesOnStructure = defaultdict(list) self._profile("initiate dicts") self.graph = nx.DiGraph() self._profile("initiate graph") self._addVertices() self._addEdges() self.print_profile() self.dirty = False def getNodeLayer(self): """ Getter for the node layer """ return self.nodeLayer def getReachLayer(self): """ Getter for the reach layer :return: """ return self.reachLayer def getNodeLayerId(self): """ Getter for the node layer's id """ return self.nodeLayerId def getReachLayerId(self): """ Getter for the reach layer's id """ return self.reachLayerId def getSnapper(self): """ Getter for the snapper """ self.snapper = QgsSnapper(self.iface.mapCanvas().mapRenderer()) snap_layer = QgsSnapper.SnapLayer() snap_layer.mLayer = self.nodeLayer snap_layer.mTolerance = 10 snap_layer.mUnitType = QgsTolerance.Pixels snap_layer.mSnapTo = QgsSnapper.SnapToVertex self.snapper.setSnapLayers([snap_layer]) return self.snapper def snapPoint(self, event): """ Snap to a point on this network :param event: A QMouseEvent """ clicked_point = QPoint(event.pos().x(), event.pos().y()) self.snapper = self.getSnapper() (_, snapped_points) = self.snapper.snapPoint(clicked_point, []) if not snapped_points: return None elif len(snapped_points) == 1: return snapped_points[0] elif len(snapped_points) > 1: point_ids = [point.snappedAtGeometry for point in snapped_points] node_features = self.getFeaturesById(self.getNodeLayer(), point_ids) # Filter wastewater nodes filtered_features = { fid: node_features.featureById(fid) for fid in node_features.asDict() if node_features.attrAsUnicode(node_features.featureById(fid), u'type') == u'wastewater_node' } # Only one wastewater node left: return this if len(filtered_features) == 1: points = (point for point in snapped_points if point.snappedAtGeometry == filtered_features.iterkeys().next()) return points[0] # Still not sure which point to take? # Are there no wastewater nodes filtered? Let the user choose from the reach points if not filtered_features: filtered_features = node_features.asDict() # Ask the user which point he wants to use actions = dict() menu = QMenu(self.iface.mapCanvas()) for _, feature in filtered_features.iteritems(): try: title = feature.attribute('description') + " (" + feature.attribute('obj_id') + ")" except TypeError: title = " (" + feature.attribute('obj_id') + ")" action = QAction(title, menu) actions[action] = point menu.addAction(action) clicked_action = menu.exec_(self.iface.mapCanvas().mapToGlobal(event.pos())) if clicked_action is not None: return actions[clicked_action] return None def shortestPath(self, start_point, end_point): """ Finds the shortes path from the start point to the end point :param start_point: The start node :param end_point: The end node :return: A (path, edges) tuple """ if self.dirty: self.createGraph() try: path = nx.algorithms.dijkstra_path(self.graph, start_point, end_point) edges = [(u, v, self.graph[u][v]) for (u, v) in zip(path[0:], path[1:])] p = (path, edges) except nx.NetworkXNoPath: print "no path found" p = ([], []) return p def getTree(self, node, reverse=False): """ Get :param node: A start node :param reverse: Should the graph be reversed (upstream search) :return: A list of edges """ if self.dirty: self.createGraph() if reverse: my_graph = self.graph.reverse() else: my_graph = self.graph # Returns pred, weight pred, _ = nx.bellman_ford(my_graph, node) edges = [(v, u, my_graph[v][u]) for (u, v) in pred.items() if v is not None] return edges def getEdgeGeometry(self, edges): """ Get the geometry for some edges :param edges: A list of edges :return: A list of polylines """ cache = self.getFeaturesById(self.reachLayer, edges) polylines = [feat.geometry().asPolyline() for feat in cache.asDict().values()] return polylines # pylint: disable=no-self-use def getFeaturesById(self, layer, ids): """ Get some features by their id """ feat_cache = QgepFeatureCache(layer) data_provider = layer.dataProvider() features = data_provider.getFeatures() for feat in features: if feat.id() in ids: feat_cache.addFeature(feat) return feat_cache # pylint: disable=no-self-use def getFeaturesByAttr(self, layer, attr, values): """ Get some features by an attribute value """ feat_cache = QgepFeatureCache(layer) data_provider = layer.dataProvider() # Batch query and filter locally features = data_provider.getFeatures() for feat in features: if feat_cache.attrAsUnicode(feat, attr) in values: feat_cache.addFeature(feat) return feat_cache def print_profile(self): """ Will print some performance profiling information """ for (name, spenttime) in self.timings: print name + ":" + str(spenttime)
class QgepGraphManager(): reachLayer = None reachLayerId = -1 nodeLayer = None nodeLayerId = -1 specialStructureLayer = None specialStructureLayerId = -1 dirty = True graph = None vertexIds = {} nodesOnStructure = defaultdict(list) # Logs performance of graph creation timings = [] def __init__( self, iface ): self.iface = iface def setReachLayer( self, reachLayer ): self.reachLayer = reachLayer self.dirty = True if reachLayer: self.reachLayerId = reachLayer.id() else: self.reachLayerId = 0 if self.nodeLayer and self.reachLayer: self.createGraph() def setNodeLayer( self, nodeLayer ): self.dirty = True self.nodeLayer = nodeLayer if nodeLayer: self.nodeLayerId = nodeLayer.id() else: self.nodeLayerId = 0 if self.nodeLayer and self.reachLayer: self.createGraph() def setSpecialStructureLayer( self, specialStructureLayer ): self.specialStructureLayer = specialStructureLayer if specialStructureLayer: self.specialStructureLayerId = specialStructureLayer.id() def _addVertices(self): nodeProvider = self.nodeLayer.dataProvider() features = nodeProvider.getFeatures() # Add all vertices for feat in features: featId = feat.id() objId = feat['obj_id'] objType = feat['type'] vertex = feat.geometry().asPoint() self.graph.add_node( featId, dict( point=vertex, objType=objType ) ) self.vertexIds[ unicode( objId ) ] = featId self._profile( "add vertices" ) def _addEdges( self ): # Add all edges (reach) reachProvider = self.reachLayer.dataProvider() features = reachProvider.getFeatures() #Loop through all reaches for feat in features: try: objId = feat['obj_id'] objType = feat['type'] fromObjId = feat['from_obj_id'] toObjId = feat['to_obj_id'] length = feat['length_calc'] ptId1 = self.vertexIds[ fromObjId ] ptId2 = self.vertexIds[ toObjId ] props = { \ 'weight': length,\ 'feature': feat.id(),\ 'baseFeature': objId,\ 'objType': objType\ } self.graph.add_edge( ptId1, ptId2, props ) except KeyError as e: print e self._profile( "add edges" ) def _profile(self,name): spenttime = 0 if len( self.timings ) != 0: spenttime = time.clock() - self.timings[-1][1] self.timings.append( (name, spenttime) ) # Creates a network graph def createGraph(self): self._profile( "create graph" ) #try: self.vertexIds = {} self.nodesOnStructure = defaultdict( list ) self._profile( "initiate dicts" ) self.graph = nx.DiGraph() self._profile( "initiate graph" ) self._addVertices() self._addEdges() self.print_profile() self.dirty = False def getNodeLayer(self): return self.nodeLayer def getSpecialStructureLayer(self): return self.specialStructureLayer def getReachLayer(self): return self.reachLayer def getNodeLayerId(self): return self.nodeLayerId def getReachLayerId(self): return self.reachLayerId def getSnapper(self): return self.snapper def snapPoint(self, event): pClicked = QPoint( event.pos().x(), event.pos().y() ) self.snapper = QgsSnapper( self.iface.mapCanvas().mapRenderer() ) snapLayer = QgsSnapper.SnapLayer() snapLayer.mLayer = self.nodeLayer snapLayer.mTolerance = 10 snapLayer.mUnitType = QgsTolerance.Pixels snapLayer.mSnapTo = QgsSnapper.SnapToVertex self.snapper.setSnapLayers( [snapLayer] ) ( _, snappedPoints ) = self.snapper.snapPoint( pClicked, [] ) if len( snappedPoints ) == 0: return None elif len( snappedPoints ) == 1: return snappedPoints[0] elif len( snappedPoints ) > 1: attrObjId = self.getNodeLayer().dataProvider().fieldNameIndex('obj_id') attrType = self.getNodeLayer().dataProvider().fieldNameIndex('type') attrDescr = self.getNodeLayer().dataProvider().fieldNameIndex('description') attributes = [attrObjId, attrType, attrDescr ] pointIds = [point.snappedAtGeometry for point in snappedPoints] nodeFeatures = self.getFeaturesById(self.getNodeLayer(), attributes, pointIds, True) # Filter wastewater nodes filteredFeatures = { id: nodeFeatures.featureById(id) for id in nodeFeatures.asDict() if nodeFeatures.attrAsUnicode( nodeFeatures.featureById(id), u'type' ) == u'wastewater_node' } # Only one wastewater node left: return this if len( filteredFeatures ) == 1: return [point for point in snappedPoints if point.snappedAtGeometry == filteredFeatures.iterkeys().next() ][0] # Still not sure which point to take? # Are there no wastewater nodes filtered? Let the user choose from the reach points if len( filteredFeatures ) == 0: filteredFeatures = nodeFeatures.asDict() # Ask the user which point he wants to use actions = dict() menu = QMenu( self.iface.mapCanvas() ) for id, feature in filteredFeatures.iteritems(): try: title = feature.attribute( 'description' ) + " (" + feature.attribute( 'obj_id' ) + ")" except TypeError: title = " (" + feature.attribute( 'obj_id' ) + ")" action = QAction( title, menu ) actions[action] = point menu.addAction( action ) clickedAction = menu.exec_( self.iface.mapCanvas().mapToGlobal( event.pos() ) ) if clickedAction is not None: return actions[clickedAction] return None # Finds the shortes path from the start point # to the end point def shortestPath(self,pStart,pEnd): if self.dirty: self.createGraph() try: path = nx.algorithms.dijkstra_path( self.graph, pStart, pEnd ) edges = [(u,v,self.graph[u][v]) for (u,v)in zip(path[0:], path[1:])] p = ( path, edges ) except nx.NetworkXNoPath: print "no path found" p = ([],[]) return p def getTree(self,node,reverse=False): if self.dirty: self.createGraph() if reverse: myGraph = self.graph.reverse() else: myGraph = self.graph # Returns pred, weight pred, _ = nx.bellman_ford(myGraph, node) edges = [(v,u,myGraph[v][u]) for (u,v) in pred.items() if v is not None] return edges def getEdgeGeometry(self, edges): cache = self.getFeaturesById(self.reachLayer, self.reachLayer.dataProvider().attributeIndexes(), edges, True) polylines = [feat.geometry().asPolyline() for feat in cache.asDict().values()] return polylines def getFeaturesById(self, layer, attributes, ids, fetchGeometry): featCache = QgepFeatureCache(layer) dataProvider = layer.dataProvider() features = dataProvider.getFeatures() for feat in features: if feat.id() in ids: featCache.addFeature( feat ) return featCache def getFeaturesByAttr(self, layer, attributes, attr, values, fetchGeometry): featCache = QgepFeatureCache(layer) dataProvider = layer.dataProvider() # Batch query and filter locally features = dataProvider.getFeatures() for feat in features: if featCache.attrAsUnicode( feat, attr ) in values : featCache.addFeature( feat ) feat = QgsFeature() return featCache def print_profile(self): for (name, spenttime) in self.timings: print name + ":" + str( spenttime )
class QgepGraphManager(QObject): """ Manages a graph """ reachLayer = None reachLayerId = -1 nodeLayer = None nodeLayerId = -1 dirty = True graph = None vertexIds = {} nodesOnStructure = defaultdict(list) # Logs performance of graph creation timings = [] def __init__(self, iface): QObject.__init__(self) self.iface = iface self.snapper = None def setReachLayer(self, reach_layer): """ Set the reach layer (edges) """ self.reachLayer = reach_layer self.dirty = True if reach_layer: self.reachLayerId = reach_layer.id() else: self.reachLayerId = 0 if self.nodeLayer and self.reachLayer: self.createGraph() def setNodeLayer(self, node_layer): """ Set the node layer """ self.dirty = True self.nodeLayer = node_layer if node_layer: self.nodeLayerId = node_layer.id() else: self.nodeLayerId = 0 if self.nodeLayer and self.reachLayer: self.createGraph() def _addVertices(self): """ Initializes the graph with the vertices from the node layer """ node_provider = self.nodeLayer.dataProvider() features = node_provider.getFeatures() # Add all vertices for feat in features: fid = feat.id() obj_id = feat['obj_id'] obj_type = feat['type'] try: vertex = feat.geometry().asPoint() except AttributeError: # TODO Add to problem log pass self.graph.add_node( fid, dict(point=vertex, objType=obj_type, objId=obj_id)) self.vertexIds[unicode(obj_id)] = fid self._profile("add vertices") def _addEdges(self): """ Initializes the graph with the edges """ # Add all edges (reach) reach_provider = self.reachLayer.dataProvider() features = reach_provider.getFeatures() # Loop through all reaches for feat in features: try: obj_id = feat['obj_id'] obj_type = feat['type'] from_obj_id = feat['from_obj_id'] to_obj_id = feat['to_obj_id'] length = feat['length_calc'] pt_id1 = self.vertexIds[from_obj_id] pt_id2 = self.vertexIds[to_obj_id] props = { 'weight': length, 'feature': feat.id(), 'baseFeature': obj_id, 'objType': obj_type } self.graph.add_edge(pt_id1, pt_id2, props) except KeyError as e: print e self._profile("add edges") def refresh(self): """ Refreshes the network graph. It will force a refresh of the materialized views in the database and then reload and recreate the graph. """ uri = QgsDataSourceURI(self.nodeLayer.dataProvider().dataSourceUri()) db = QSqlDatabase.addDatabase( "QPSQL") # Name of the driver -- doesn't change str_connect_option = "requiressl=0;service=" + uri.service() db.setConnectOptions(str_connect_option) if not db.open(): self.iface.messageBar().pushMessage(self.tr("Warning"), db.lastError().text(), level=QgsMessageBar.CRITICAL) query_template = "REFRESH MATERIALIZED VIEW qgep.vw_network_segment;" query = QSqlQuery(db) if not query.exec_(query_template): str_result = query.lastError().text() self.iface.messageBar().pushMessage(self.tr("Warning"), str_result, level=QgsMessageBar.CRITICAL) else: self.iface.messageBar().pushMessage( self.tr("Success"), "vw_network_segment successfully updated", level=QgsMessageBar.SUCCESS, duration=2) query_template = "REFRESH MATERIALIZED VIEW qgep.vw_network_node;" query = QSqlQuery(db) if not query.exec_(query_template): str_result = query.lastError().text() self.iface.messageBar().pushMessage(self.tr("Warning"), str_result, level=QgsMessageBar.CRITICAL) else: self.iface.messageBar().pushMessage( self.tr("Success"), "vw_network_node successfully updated", level=QgsMessageBar.SUCCESS, duration=2) # recreate networkx graph self.graph.clear() self.createGraph() def _profile(self, name): """ Adds a performance profile snapshot with the given name """ spenttime = 0 if self.timings: spenttime = time.clock() - self.timings[-1][1] self.timings.append((name, spenttime)) # Creates a network graph def createGraph(self): """ Create a graph """ self._profile("create graph") # try: self.vertexIds = {} self.nodesOnStructure = defaultdict(list) self._profile("initiate dicts") self.graph = nx.DiGraph() self._profile("initiate graph") self._addVertices() self._addEdges() self.print_profile() self.dirty = False def getNodeLayer(self): """ Getter for the node layer """ return self.nodeLayer def getReachLayer(self): """ Getter for the reach layer :return: """ return self.reachLayer def getNodeLayerId(self): """ Getter for the node layer's id """ return self.nodeLayerId def getReachLayerId(self): """ Getter for the reach layer's id """ return self.reachLayerId def getSnapper(self): """ Getter for the snapper """ self.snapper = QgsSnapper(self.iface.mapCanvas().mapRenderer()) snap_layer = QgsSnapper.SnapLayer() snap_layer.mLayer = self.nodeLayer snap_layer.mTolerance = 10 snap_layer.mUnitType = QgsTolerance.Pixels snap_layer.mSnapTo = QgsSnapper.SnapToVertex self.snapper.setSnapLayers([snap_layer]) return self.snapper def snapPoint(self, event): """ Snap to a point on this network :param event: A QMouseEvent """ clicked_point = QPoint(event.pos().x(), event.pos().y()) self.snapper = self.getSnapper() (_, snapped_points) = self.snapper.snapPoint(clicked_point, []) if not snapped_points: return None elif len(snapped_points) == 1: return snapped_points[0] elif len(snapped_points) > 1: point_ids = [point.snappedAtGeometry for point in snapped_points] node_features = self.getFeaturesById(self.getNodeLayer(), point_ids) # Filter wastewater nodes filtered_features = { fid: node_features.featureById(fid) for fid in node_features.asDict() if node_features.attrAsUnicode(node_features.featureById(fid), u'type') == u'wastewater_node' } # Only one wastewater node left: return this if len(filtered_features) == 1: points = (point for point in snapped_points if point.snappedAtGeometry == filtered_features.iterkeys().next()) return points[0] # Still not sure which point to take? # Are there no wastewater nodes filtered? Let the user choose from the reach points if not filtered_features: filtered_features = node_features.asDict() # Ask the user which point he wants to use actions = dict() menu = QMenu(self.iface.mapCanvas()) for _, feature in filtered_features.iteritems(): try: title = feature.attribute( 'description') + " (" + feature.attribute( 'obj_id') + ")" except TypeError: title = " (" + feature.attribute('obj_id') + ")" action = QAction(title, menu) actions[action] = point menu.addAction(action) clicked_action = menu.exec_(self.iface.mapCanvas().mapToGlobal( event.pos())) if clicked_action is not None: return actions[clicked_action] return None def shortestPath(self, start_point, end_point): """ Finds the shortes path from the start point to the end point :param start_point: The start node :param end_point: The end node :return: A (path, edges) tuple """ if self.dirty: self.createGraph() try: path = nx.algorithms.dijkstra_path(self.graph, start_point, end_point) edges = [(u, v, self.graph[u][v]) for (u, v) in zip(path[0:], path[1:])] p = (path, edges) except nx.NetworkXNoPath: print "no path found" p = ([], []) return p def getTree(self, node, reverse=False): """ Get :param node: A start node :param reverse: Should the graph be reversed (upstream search) :return: A list of edges """ if self.dirty: self.createGraph() if reverse: my_graph = self.graph.reverse() else: my_graph = self.graph # Returns pred, weight pred, _ = nx.bellman_ford(my_graph, node) edges = [(v, u, my_graph[v][u]) for (u, v) in pred.items() if v is not None] nodes = [ my_graph.node[n] for n in set(pred.keys() + pred.values()) if n is not None ] return nodes, edges def getEdgeGeometry(self, edges): """ Get the geometry for some edges :param edges: A list of edges :return: A list of polylines """ cache = self.getFeaturesById(self.reachLayer, edges) polylines = [ feat.geometry().asPolyline() for feat in cache.asDict().values() ] return polylines # pylint: disable=no-self-use def getFeaturesById(self, layer, ids): """ Get some features by their id """ feat_cache = QgepFeatureCache(layer) data_provider = layer.dataProvider() features = data_provider.getFeatures() for feat in features: if feat.id() in ids: feat_cache.addFeature(feat) return feat_cache # pylint: disable=no-self-use def getFeaturesByAttr(self, layer, attr, values): """ Get some features by an attribute value """ feat_cache = QgepFeatureCache(layer) data_provider = layer.dataProvider() # Batch query and filter locally features = data_provider.getFeatures() for feat in features: if feat_cache.attrAsUnicode(feat, attr) in values: feat_cache.addFeature(feat) return feat_cache def print_profile(self): """ Will print some performance profiling information """ for (name, spenttime) in self.timings: print name + ":" + str(spenttime)
def __init__( self, iface ): self.iface = iface self.snapper = QgsSnapper( self.iface.mapCanvas().mapRenderer() )