def test_reset(self): self.r.compute_path(dest, origin) self.r.reset() self.assertEqual(self.r.path, None, "Fail to reset the Path computation object") self.assertEqual(self.r.path_nodes, None, "Fail to reset the Path computation object") self.assertEqual(self.r.path_link_directions, None, "Fail to reset the Path computation object") self.assertEqual(self.r.milepost, None, "Fail to reset the Path computation object") self.assertEqual(self.r.predecessors.max(), -1, "Fail to reset the Path computation object") self.assertEqual(self.r.predecessors.min(), -1, "Fail to reset the Path computation object") self.assertEqual(self.r.connectors.max(), -1, "Fail to reset the Path computation object") self.assertEqual(self.r.connectors.min(), -1, "Fail to reset the Path computation object") if self.r.skims is not None: self.assertEqual(self.r.skims.max(), np.inf, "Fail to reset the Path computation object") self.assertEqual(self.r.skims.min(), np.inf, "Fail to reset the Path computation object") new_r = PathResults() with self.assertRaises(ValueError): new_r.reset()
class TestPathResults(TestCase): def test_prepare(self): # graph self.g = Graph() self.g.load_from_disk(test_graph) self.g.set_graph(cost_field="distance") self.r = PathResults() try: self.r.prepare(self.g) except Exception as err: self.fail("Path result preparation failed - {}".format( err.__str__())) def test_reset(self): self.test_prepare() try: self.r.reset() except Exception as err: self.fail("Path result resetting failed - {}".format( err.__str__())) def test_update_trace(self): self.test_prepare() try: self.r.reset() except Exception as err: self.fail("Path result resetting failed - {}".format( err.__str__())) path_computation(origin, dest, self.g, self.r) if list(self.r.path) != [53, 52, 13]: self.fail("Path computation failed. Wrong sequence of links") if list(self.r.path_nodes) != [5, 168, 166, 27]: self.fail("Path computation failed. Wrong sequence of path nodes") if list(self.r.milepost) != [0, 341, 1398, 2162]: self.fail("Path computation failed. Wrong milepost results")
class TestPathResults(TestCase): def test_prepare(self): # graph self.g = Graph() self.g.load_from_disk(test_graph) self.g.set_graph(cost_field='distance', skim_fields=None) self.r = PathResults() try: self.r.prepare(self.g) except: self.fail('Path result preparation failed') def test_reset(self): self.test_prepare() try: self.r.reset() except: self.fail('Path result resetting failed') def test_update_trace(self): self.test_prepare() try: self.r.reset() except: self.fail('Path result resetting failed') path_computation(origin, dest, self.g, self.r) if list(self.r.path) != [53, 52, 13]: self.fail('Path computation failed. Wrong sequence of links') if list(self.r.path_nodes) != [5, 168, 166, 27]: self.fail('Path computation failed. Wrong sequence of path nodes') if list(self.r.milepost) != [0, 341, 1398, 2162]: self.fail('Path computation failed. Wrong milepost results')
class ShortestPathDialog(QtGui.QDialog, Ui_compute_path): def __init__(self, iface): QDialog.__init__(self) QtGui.QDialog.__init__(self, None, QtCore.Qt.WindowStaysOnTopHint) self.iface = iface self.setupUi(self) self.field_types = {} self.centroids = None self.node_layer = None self.line_layer = None self.node_keys = None self.node_fields = None self.index = None self.matrix = None self.clickTool = PointTool(self.iface.mapCanvas()) self.path = standard_path() self.node_id = None self.res = PathResults() self.link_features = None self.do_dist_matrix.setEnabled(False) self.load_graph_from_file.clicked.connect( self.prepare_graph_and_network) self.from_but.clicked.connect(self.search_for_point_from) self.to_but.clicked.connect(self.search_for_point_to) self.do_dist_matrix.clicked.connect(self.produces_path) def prepare_graph_and_network(self): dlg2 = LoadGraphLayerSettingDialog(self.iface) dlg2.show() dlg2.exec_() if dlg2.error is None and dlg2.graph_ok: self.link_features = dlg2.link_features self.line_layer = dlg2.line_layer self.node_layer = dlg2.node_layer self.node_keys = dlg2.node_keys self.node_id = dlg2.node_id self.node_fields = dlg2.node_fields self.index = dlg2.index self.graph = dlg2.graph self.res.prepare(self.graph) self.do_dist_matrix.setEnabled(True) def clear_memory_layer(self): self.link_features = None def search_for_point_from(self): self.iface.mapCanvas().setMapTool(self.clickTool) QObject.connect(self.clickTool, SIGNAL("clicked"), self.fill_path_from) self.from_but.setEnabled(False) def search_for_point_to(self): self.iface.mapCanvas().setMapTool(self.clickTool) QObject.connect(self.clickTool, SIGNAL("clicked"), self.fill_path_to) self.to_but.setEnabled(False) def search_for_point_to_after_from(self): self.iface.mapCanvas().setMapTool(self.clickTool) QObject.connect(self.clickTool, SIGNAL("clicked"), self.fill_path_to) def fill_path_to(self): self.to_node = self.find_point() self.path_to.setText(str(self.to_node)) self.to_but.setEnabled(True) def fill_path_from(self): self.from_node = self.find_point() self.path_from.setText(str(self.from_node)) self.from_but.setEnabled(True) self.search_for_point_to_after_from() def find_point(self): try: point = self.clickTool.point nearest = self.index.nearestNeighbor(point, 1) self.iface.mapCanvas().setMapTool(None) self.clickTool = PointTool(self.iface.mapCanvas()) node_id = self.node_keys[nearest[0]] index_field = self.node_fields.index(self.node_id) node_actual_id = node_id[index_field] return node_actual_id except: pass def produces_path(self): self.to_but.setEnabled(True) if self.path_from.text().isdigit() and self.path_to.text().isdigit(): self.res.reset() path_computation(int(self.path_from.text()), int(self.path_to.text()), self.graph, self.res) if self.res.path is not None: ## If you want to do selections instead of new layers, this is how to do it # f = self.cb_link_id_field.currentText() # t = '' # for k in self.res.path[:-1]: # t = t + f + "=" + str(k) + ' or ' # t = t + f + "=" + str(self.res.path[-1]) # expr = QgsExpression(t) # it = self.line_layer.getFeatures(QgsFeatureRequest(expr)) # # ids = [i.id() for i in it] # self.line_layer.setSelectedFeatures(ids) # If you want to create new layers # This way is MUCH faster crs = self.line_layer.dataProvider().crs().authid() vl = QgsVectorLayer( "LineString?crs={}".format(crs), self.path_from.text() + " to " + self.path_to.text(), "memory") pr = vl.dataProvider() # add fields pr.addAttributes(self.line_layer.dataProvider().fields()) vl.updateFields( ) # tell the vector layer to fetch changes from the provider # add a feature all_links = [] for k in self.res.path: fet = self.link_features[k] all_links.append(fet) # add all links to the temp layer pr.addFeatures(all_links) # add layer to the map QgsMapLayerRegistry.instance().addMapLayer(vl) # format the layer with a thick line registry = QgsSymbolLayerV2Registry.instance() lineMeta = registry.symbolLayerMetadata("SimpleLine") symbol = QgsLineSymbolV2() lineLayer = lineMeta.createSymbolLayer({ 'width': '1', 'color': self.random_rgb(), 'offset': '0', 'penstyle': 'solid', 'use_custom_dash': '0', 'joinstyle': 'bevel', 'capstyle': 'square' }) symbol.deleteSymbolLayer(0) symbol.appendSymbolLayer(lineLayer) renderer = QgsSingleSymbolRendererV2(symbol) vl.setRendererV2(renderer) qgis.utils.iface.mapCanvas().refresh() else: qgis.utils.iface.messageBar().pushMessage( "No path between " + self.path_from.text() + ' and ' + self.path_to.text(), '', level=3) def random_rgb(self): rgb = '' for i in range(3): rgb = rgb + str(randint(0, 255)) + ',' return rgb[:-1] def exit_procedure(self): self.close()
missing_nodes = np.array(missing_nodes) # And prepare the path computation structure res = PathResults() res.prepare(graph) # %% # Now we can compute all the path islands we have islands = [] idx_islands = 0 # while missing_nodes.shape[0] >= 2: print(datetime.now().strftime("%H:%M:%S"), f' - Computing island: {idx_islands}') res.reset() res.compute_path(missing_nodes[0], missing_nodes[1]) res.predecessors[graph.nodes_to_indices[missing_nodes[0]]] = 0 connected = graph.all_nodes[np.where(res.predecessors >= 0)] connected = np.intersect1d(missing_nodes, connected) missing_nodes = np.setdiff1d(missing_nodes, connected) print(f' Nodes to find: {missing_nodes.shape[0]:,}') df = pd.DataFrame({'node_id': connected, 'island': idx_islands}) islands.append(df) idx_islands += 1 print(f'\nWe found {idx_islands} islands') # %% # consolidate everything into a single DataFrame islands = pd.concat(islands)
class ShortestPathDialog(QtWidgets.QDialog, FORM_CLASS): clickTool = PointTool(iface.mapCanvas()) def __init__(self, iface, project: Project, link_layer: QgsVectorLayer, node_layer: QgsVectorLayer) -> None: # QtWidgets.QDialog.__init__(self) QtWidgets.QDialog.__init__(self) self.iface = iface self.project = project self.setupUi(self) self.field_types = {} self.centroids = None self.node_layer = node_layer self.line_layer = link_layer self.node_keys = {} self.node_fields = None self.index = None self.matrix = None self.path = standard_path() self.node_id = None self.res = PathResults() self.link_features = None self.do_dist_matrix.setEnabled(False) self.from_but.setEnabled(False) self.to_but.setEnabled(False) self.load_graph_from_file.clicked.connect( self.prepare_graph_and_network) self.from_but.clicked.connect(self.search_for_point_from) self.to_but.clicked.connect(self.search_for_point_to) self.do_dist_matrix.clicked.connect(self.produces_path) def prepare_graph_and_network(self): self.do_dist_matrix.setText('Loading data') self.from_but.setEnabled(False) self.to_but.setEnabled(False) dlg2 = LoadGraphLayerSettingDialog(self.iface, self.project) dlg2.show() dlg2.exec_() if len(dlg2.error) < 1 and len(dlg2.mode) > 0: self.mode = dlg2.mode self.minimize_field = dlg2.minimize_field if not self.mode in self.project.network.graphs: self.project.network.build_graphs() if dlg2.remove_chosen_links: self.graph = self.project.network.graphs.pop(self.mode) else: self.graph = self.project.network.graphs[self.mode] self.graph.set_graph(self.minimize_field) self.graph.set_skimming(self.minimize_field) self.graph.set_blocked_centroid_flows(dlg2.block_connector) if dlg2.remove_chosen_links: idx = self.line_layer.dataProvider().fieldNameIndex('link_id') remove = [ feat.attributes()[idx] for feat in self.line_layer.selectedFeatures() ] self.graph.exclude_links(remove) self.res.prepare(self.graph) self.node_fields = [ field.name() for field in self.node_layer.dataProvider().fields().toList() ] self.index = QgsSpatialIndex() for feature in self.node_layer.getFeatures(): self.index.addFeature(feature) self.node_keys[feature.id()] = feature.attributes() idx = self.line_layer.dataProvider().fieldNameIndex('link_id') self.link_features = {} for feat in self.line_layer.getFeatures(): link_id = feat.attributes()[idx] self.link_features[link_id] = feat self.do_dist_matrix.setText('Display') self.do_dist_matrix.setEnabled(True) self.from_but.setEnabled(True) self.to_but.setEnabled(True) def clear_memory_layer(self): self.link_features = None def search_for_point_from(self): self.clickTool.clicked.connect(self.fill_path_from) self.iface.mapCanvas().setMapTool(self.clickTool) self.from_but.setEnabled(False) def search_for_point_to(self): self.iface.mapCanvas().setMapTool(self.clickTool) self.clickTool.clicked.connect(self.fill_path_to) self.to_but.setEnabled(False) def search_for_point_to_after_from(self): self.iface.mapCanvas().setMapTool(self.clickTool) self.clickTool.clicked.connect(self.fill_path_to) def fill_path_to(self): self.to_node = self.find_point() self.path_to.setText(str(self.to_node)) self.to_but.setEnabled(True) @QtCore.pyqtSlot() def fill_path_from(self): self.from_node = self.find_point() self.path_from.setText(str(self.from_node)) self.from_but.setEnabled(True) self.search_for_point_to_after_from() def find_point(self): try: point = self.clickTool.point nearest = self.index.nearestNeighbor(point, 1) self.iface.mapCanvas().setMapTool(None) self.clickTool = PointTool(self.iface.mapCanvas()) node_id = self.node_keys[nearest[0]] index_field = self.node_fields.index('node_id') node_actual_id = node_id[index_field] return node_actual_id except: pass def produces_path(self): self.to_but.setEnabled(True) if self.path_from.text().isdigit() and self.path_to.text().isdigit(): self.res.reset() path_computation(int(self.path_from.text()), int(self.path_to.text()), self.graph, self.res) if self.res.path is not None: # If you want to do selections instead of new layers if self.rdo_selection.isChecked(): self.create_path_with_selection() # If you want to create new layers else: self.create_path_with_scratch_layer() else: qgis.utils.iface.messageBar().pushMessage( "No path between " + self.path_from.text() + " and " + self.path_to.text(), "", level=3) def create_path_with_selection(self): f = 'link_id' t = " or ".join([f"{f}={k}" for k in self.res.path]) self.line_layer.selectByExpression(t) def create_path_with_scratch_layer(self): crs = self.line_layer.dataProvider().crs().authid() vl = QgsVectorLayer( "LineString?crs={}".format(crs), self.path_from.text() + " to " + self.path_to.text(), "memory") pr = vl.dataProvider() # add fields pr.addAttributes(self.line_layer.dataProvider().fields()) vl.updateFields( ) # tell the vector layer to fetch changes from the provider # add a feature all_links = [] for k in self.res.path: fet = self.link_features[k] all_links.append(fet) # add all links to the temp layer pr.addFeatures(all_links) # add layer to the map QgsProject.instance().addMapLayer(vl) symbol = vl.renderer().symbol() symbol.setWidth(1) qgis.utils.iface.mapCanvas().refresh() def exit_procedure(self): self.close()
class TestPathResults(TestCase): def setUp(self) -> None: self.project = create_example( join(gettempdir(), "test_set_pce_" + uuid4().hex)) self.project.network.build_graphs() self.g = self.project.network.graphs["c"] # type: Graph self.g.set_graph("free_flow_time") self.g.set_blocked_centroid_flows(False) self.matrix = self.project.matrices.get_matrix("demand_omx") self.matrix.computational_view() self.r = PathResults() self.r.prepare(self.g) def tearDown(self) -> None: self.project.close() self.matrix.close() del self.r def test_reset(self): self.r.compute_path(dest, origin) self.r.reset() self.assertEqual(self.r.path, None, "Fail to reset the Path computation object") self.assertEqual(self.r.path_nodes, None, "Fail to reset the Path computation object") self.assertEqual(self.r.path_link_directions, None, "Fail to reset the Path computation object") self.assertEqual(self.r.milepost, None, "Fail to reset the Path computation object") self.assertEqual(self.r.predecessors.max(), -1, "Fail to reset the Path computation object") self.assertEqual(self.r.predecessors.min(), -1, "Fail to reset the Path computation object") self.assertEqual(self.r.connectors.max(), -1, "Fail to reset the Path computation object") self.assertEqual(self.r.connectors.min(), -1, "Fail to reset the Path computation object") if self.r.skims is not None: self.assertEqual(self.r.skims.max(), np.inf, "Fail to reset the Path computation object") self.assertEqual(self.r.skims.min(), np.inf, "Fail to reset the Path computation object") new_r = PathResults() with self.assertRaises(ValueError): new_r.reset() def test_compute_paths(self): path_computation(5, 2, self.g, self.r) self.assertEqual(list(self.r.path), [12, 14], "Path computation failed. Wrong sequence of links") self.assertEqual(list(self.r.path_link_directions), [1, 1], "Path computation failed. Wrong link directions") self.assertEqual( list(self.r.path_nodes), [5, 6, 2], "Path computation failed. Wrong sequence of path nodes") self.assertEqual(list(self.r.milepost), [0, 4, 9], "Path computation failed. Wrong milepost results") def test_compute_with_skimming(self): r = PathResults() self.g.set_skimming("free_flow_time") r.prepare(self.g) r.compute_path(origin, dest) self.assertEqual(r.milepost[-1], r.skims[dest], "Skims computed wrong when computing path") def test_update_trace(self): self.r.compute_path(origin, 2) self.r.update_trace(10) self.assertEqual(list(self.r.path), [13, 25], "Path update failed. Wrong sequence of links") self.assertEqual(list(self.r.path_link_directions), [1, 1], "Path update failed. Wrong link directions") self.assertEqual(list(self.r.path_nodes), [5, 9, 10], "Path update failed. Wrong sequence of path nodes") self.assertEqual(list(self.r.milepost), [0, 5, 8], "Path update failed. Wrong milepost results")
class TestPathResults(TestCase): def setUp(self) -> None: # graph self.g = Graph() self.g.load_from_disk(test_graph) self.g.set_graph(cost_field="distance") self.r = PathResults() try: self.r.prepare(self.g) except Exception as err: self.fail("Path result preparation failed - {}".format(err.__str__())) def test_reset(self): self.r.compute_path(dest, origin) self.r.reset() self.assertEqual(self.r.path, None, 'Fail to reset the Path computation object') self.assertEqual(self.r.path_nodes, None, 'Fail to reset the Path computation object') self.assertEqual(self.r.path_link_directions, None, 'Fail to reset the Path computation object') self.assertEqual(self.r.milepost, None, 'Fail to reset the Path computation object') self.assertEqual(self.r.predecessors.max(), -1, 'Fail to reset the Path computation object') self.assertEqual(self.r.predecessors.min(), -1, 'Fail to reset the Path computation object') self.assertEqual(self.r.connectors.max(), -1, 'Fail to reset the Path computation object') self.assertEqual(self.r.connectors.min(), -1, 'Fail to reset the Path computation object') self.assertEqual(self.r.skims.max(), np.inf, 'Fail to reset the Path computation object') self.assertEqual(self.r.skims.min(), np.inf, 'Fail to reset the Path computation object') new_r = PathResults() with self.assertRaises(ValueError): new_r.reset() def test_compute_paths(self): path_computation(origin, dest, self.g, self.r) if list(self.r.path) != [53, 52, 13]: self.fail("Path computation failed. Wrong sequence of links") if list(self.r.path_nodes) != [5, 168, 166, 27]: self.fail("Path computation failed. Wrong sequence of path nodes") if list(self.r.milepost) != [0, 341, 1398, 2162]: self.fail("Path computation failed. Wrong milepost results") self.r.compute_path(origin, dest) if list(self.r.path) != [53, 52, 13]: self.fail("Path computation failed. Wrong sequence of links") if list(self.r.path_nodes) != [5, 168, 166, 27]: self.fail("Path computation failed. Wrong sequence of path nodes") if list(self.r.milepost) != [0, 341, 1398, 2162]: self.fail("Path computation failed. Wrong milepost results") if list(self.r.path_link_directions) != [-1, -1, -1]: self.fail("Path computation failed. Wrong link directions") self.r.compute_path(dest, origin) if list(self.r.path_link_directions) != [1, 1, 1]: self.fail("Path computation failed. Wrong link directions") def test_update_trace(self): self.r.compute_path(origin, dest - 1) self.r.update_trace(dest) if list(self.r.path) != [53, 52, 13]: self.fail("Path computation failed. Wrong sequence of links") if list(self.r.path_nodes) != [5, 168, 166, 27]: self.fail("Path computation failed. Wrong sequence of path nodes") if list(self.r.milepost) != [0, 341, 1398, 2162]: self.fail("Path computation failed. Wrong milepost results") if list(self.r.path_link_directions) != [-1, -1, -1]: self.fail("Path computation failed. Wrong link directions")
class ShortestPathDialog(QtWidgets.QDialog, FORM_CLASS): clickTool = PointTool(iface.mapCanvas()) def __init__(self, iface): # QtWidgets.QDialog.__init__(self) QtWidgets.QDialog.__init__(self, None, Qt.WindowStaysOnTopHint) self.iface = iface self.setupUi(self) self.field_types = {} self.centroids = None self.node_layer = None self.line_layer = None self.node_keys = None self.node_fields = None self.index = None self.matrix = None self.path = standard_path() self.node_id = None self.res = PathResults() self.link_features = None self.do_dist_matrix.setEnabled(False) self.load_graph_from_file.clicked.connect( self.prepare_graph_and_network) self.from_but.clicked.connect(self.search_for_point_from) self.to_but.clicked.connect(self.search_for_point_to) self.do_dist_matrix.clicked.connect(self.produces_path) def prepare_graph_and_network(self): dlg2 = LoadGraphLayerSettingDialog(self.iface) dlg2.show() dlg2.exec_() if dlg2.error is None and dlg2.graph_ok: self.link_features = dlg2.link_features self.line_layer = dlg2.line_layer self.node_layer = dlg2.node_layer self.node_keys = dlg2.node_keys self.node_id = dlg2.node_id self.node_fields = dlg2.node_fields self.index = dlg2.index self.graph = dlg2.graph self.res.prepare(self.graph) self.do_dist_matrix.setEnabled(True) def clear_memory_layer(self): self.link_features = None def search_for_point_from(self): self.clickTool.clicked.connect(self.fill_path_from) self.iface.mapCanvas().setMapTool(self.clickTool) self.from_but.setEnabled(False) def search_for_point_to(self): self.iface.mapCanvas().setMapTool(self.clickTool) self.clickTool.clicked.connect(self.fill_path_to) self.to_but.setEnabled(False) def search_for_point_to_after_from(self): self.iface.mapCanvas().setMapTool(self.clickTool) self.clickTool.clicked.connect(self.fill_path_to) def fill_path_to(self): self.to_node = self.find_point() self.path_to.setText(str(self.to_node)) self.to_but.setEnabled(True) @QtCore.pyqtSlot() def fill_path_from(self): self.from_node = self.find_point() self.path_from.setText(str(self.from_node)) self.from_but.setEnabled(True) self.search_for_point_to_after_from() def find_point(self): try: point = self.clickTool.point nearest = self.index.nearestNeighbor(point, 1) self.iface.mapCanvas().setMapTool(None) self.clickTool = PointTool(self.iface.mapCanvas()) node_id = self.node_keys[nearest[0]] index_field = self.node_fields.index(self.node_id) node_actual_id = node_id[index_field] return node_actual_id except: pass def produces_path(self): self.to_but.setEnabled(True) if self.path_from.text().isdigit() and self.path_to.text().isdigit(): self.res.reset() path_computation(int(self.path_from.text()), int(self.path_to.text()), self.graph, self.res) if self.res.path is not None: ## If you want to do selections instead of new layers, this is how to do it # f = self.cb_link_id_field.currentText() # t = '' # for k in self.res.path[:-1]: # t = t + f + "=" + str(k) + ' or ' # t = t + f + "=" + str(self.res.path[-1]) # expr = QgsExpression(t) # it = self.line_layer.getFeatures(QgsFeatureRequest(expr)) # # ids = [i.id() for i in it] # self.line_layer.setSelectedFeatures(ids) # If you want to create new layers # This way is MUCH faster crs = self.line_layer.dataProvider().crs().authid() vl = QgsVectorLayer( "LineString?crs={}".format(crs), self.path_from.text() + " to " + self.path_to.text(), "memory") pr = vl.dataProvider() # add fields pr.addAttributes(self.line_layer.dataProvider().fields()) vl.updateFields( ) # tell the vector layer to fetch changes from the provider # add a feature all_links = [] for k in self.res.path: fet = self.link_features[k] all_links.append(fet) # add all links to the temp layer pr.addFeatures(all_links) # add layer to the map QgsProject.instance().addMapLayer(vl) symbol = vl.renderer().symbol() symbol.setWidth(1) qgis.utils.iface.mapCanvas().refresh() else: qgis.utils.iface.messageBar().pushMessage( "No path between " + self.path_from.text() + ' and ' + self.path_to.text(), '', level=3) def exit_procedure(self): self.close()
class TSPDialog(QtWidgets.QDialog, FORM_CLASS): def __init__(self, iface, project: Project, link_layer, node_layer): QtWidgets.QDialog.__init__(self) self.iface = iface self.setupUi(self) self.project = project self.link_layer = link_layer self.node_layer = node_layer self.all_modes = {} self.worker_thread: TSPProcedure = None self.but_run.clicked.connect(self.run) self.res = PathResults() self.rdo_selected.clicked.connect(self.populate_node_source) self.rdo_centroids.clicked.connect(self.populate_node_source) self.populate() self.populate_node_source() def populate_node_source(self): self.cob_start.clear() if self.rdo_selected.isChecked(): centroids = self.selected_nodes() else: curr = self.project.network.conn.cursor() curr.execute('select node_id from nodes where is_centroid=1;') centroids = [i[0] for i in curr.fetchall()] for i in centroids: self.cob_start.addItem(str(i)) def populate(self): curr = self.project.network.conn.cursor() curr.execute("""select mode_name, mode_id from modes""") for x in curr.fetchall(): self.cob_mode.addItem(f'{x[0]} ({x[1]})') self.all_modes[f'{x[0]} ({x[1]})'] = x[1] for f in self.project.network.skimmable_fields(): self.cob_minimize.addItem(f) def selected_nodes(self) -> list: idx = self.node_layer.dataProvider().fieldNameIndex('node_id') c = [ int(feat.attributes()[idx]) for feat in self.node_layer.selectedFeatures() ] return sorted(c) def run(self): error = None md = self.all_modes[self.cob_mode.currentText()] self.project.network.build_graphs() self.graph = self.project.network.graphs[md] if self.rdo_selected.isChecked(): centroids = self.selected_nodes() if len(centroids) < 3: qgis.utils.iface.messageBar().pushMessage( "You need at least three selected nodes. ", '', level=3) return centroids = np.array(centroids).astype(np.int64) self.graph.prepare_graph(centroids=centroids) else: if self.project.network.count_centroids() < 3: qgis.utils.iface.messageBar().pushMessage( "You need at least three centroids. ", '', level=3) return self.graph.set_graph(self.cob_minimize.currentText() ) # let's say we want to minimize time self.graph.set_blocked_centroid_flows(self.chb_block.isChecked()) self.graph.set_skimming([self.cob_minimize.currentText() ]) # And will skim time and distance depot = int(self.cob_start.currentText()) vehicles = 1 self.res.prepare(self.graph) self.worker_thread = TSPProcedure(qgis.utils.iface.mainWindow(), self.graph, depot, vehicles) self.run_thread() def run_thread(self): self.worker_thread.ProgressValue.connect( self.progress_value_from_thread) self.worker_thread.ProgressMaxValue.connect( self.progress_range_from_thread) self.worker_thread.ProgressText.connect(self.progress_text_from_thread) self.worker_thread.finished_threaded_procedure.connect(self.finished) self.worker_thread.start() self.exec_() def progress_range_from_thread(self, value): self.progressbar.setRange(0, value) def progress_text_from_thread(self, value): self.progress_label.setText(value) def progress_value_from_thread(self, value): self.progressbar.setValue(value) def finished(self, procedure): ns = self.worker_thread.node_sequence if len(ns) < 2: return all_links = [] for i in range(1, len(ns)): self.res.reset() path_computation(ns[i - 1], ns[i], self.graph, self.res) all_links.extend(list(self.res.path)) if self.rdo_new_layer.isChecked(): self.create_path_with_scratch_layer(all_links) else: self.create_path_with_selection(all_links) self.close() if self.worker_thread.report is not None: dlg2 = ReportDialog(self.iface, self.worker_thread.report) dlg2.show() dlg2.exec_() def create_path_with_selection(self, all_links): f = 'link_id' t = " or ".join([f"{f}={k}" for k in all_links]) self.link_layer.selectByExpression(t) def create_path_with_scratch_layer(self, path_links): # Create TSP route crs = self.link_layer.dataProvider().crs().authid() vl = QgsVectorLayer(f"LineString?crs={crs}", 'TSP Solution', "memory") pr = vl.dataProvider() # add fields pr.addAttributes(self.link_layer.dataProvider().fields()) vl.updateFields( ) # tell the vector layer to fetch changes from the provider idx = self.link_layer.dataProvider().fieldNameIndex('link_id') self.link_features = {} for feat in self.link_layer.getFeatures(): link_id = feat.attributes()[idx] self.link_features[link_id] = feat # add a feature all_links = [] for k in path_links: fet = self.link_features[k] all_links.append(fet) # add all links to the temp layer pr.addFeatures(all_links) # add layer to the map QgsProject.instance().addMapLayer(vl) symbol = vl.renderer().symbol() symbol.setWidth(1.6) qgis.utils.iface.mapCanvas().refresh() # Create TSP stops crs = self.node_layer.dataProvider().crs().authid() nl = QgsVectorLayer(f"Point?crs={crs}", 'TSP Stops', "memory") pn = nl.dataProvider() # add fields pn.addAttributes(self.node_layer.dataProvider().fields()) nl.updateFields( ) # tell the vector layer to fetch changes from the provider idx = self.node_layer.dataProvider().fieldNameIndex('node_id') self.node_features = {} for feat in self.node_layer.getFeatures(): node_id = feat.attributes()[idx] self.node_features[node_id] = feat # add the feature stop_nodes = [] seq = {} for i, k in enumerate(self.worker_thread.node_sequence[:-1]): fet = self.node_features[k] stop_nodes.append(fet) seq[k] = i + 1 # add all links to the temp layer pn.addFeatures(stop_nodes) # Goes back and adds the order of visitation for each node pn.addAttributes([QgsField('sequence', QVariant.Int)]) nl.updateFields() sdx = nl.dataProvider().fieldNameIndex('sequence') nl.startEditing() for feat in nl.getFeatures(): node_id = feat.attributes()[idx] nl.changeAttributeValue(feat.id(), sdx, seq[node_id]) nl.commitChanges() # add layer to the map QgsProject.instance().addMapLayer(nl) symbol = QgsMarkerSymbol.createSimple({'name': 'star', 'color': 'red'}) symbol.setSize(6) nl.renderer().setSymbol(symbol) qgis.utils.iface.mapCanvas().refresh()