From 39131ba845a6e98ac4d75388daaa2b13ee268b1a Mon Sep 17 00:00:00 2001 From: cvvergara Date: Wed, 15 Oct 2025 11:29:04 -0600 Subject: [PATCH 01/10] Pump up to version 4.0 --- help/source/conf.py | 10 ++++------ help/source/index.rst | 5 ++--- metadata.txt | 10 +++++++--- pb_tool.cfg | 11 ----------- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/help/source/conf.py b/help/source/conf.py index a5aca08..1beaeb0 100644 --- a/help/source/conf.py +++ b/help/source/conf.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # -# PhotoLinker documentation build configuration file, created by -# sphinx-quickstart on Sun Feb 12 17:11:03 2012. -# +# pgRoutingLayer documentation build configuration file, created by # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this @@ -25,7 +23,7 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -48,9 +46,9 @@ # built documents. # # The short X.Y version. -version = '3.0' +version = '4.0' # The full version, including alpha/beta/rc tags. -release = '3.0' +release = '4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/help/source/index.rst b/help/source/index.rst index 553c567..9b53dd1 100644 --- a/help/source/index.rst +++ b/help/source/index.rst @@ -1,9 +1,8 @@ -.. PhotoLinker documentation master file, created by - sphinx-quickstart on Sun Feb 12 17:11:03 2012. +.. pgRoutingLayer documentation master file, created by You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to PhotoLinker's documentation! +Welcome to pgRoutingLayer's documentation! ============================================ Contents: diff --git a/metadata.txt b/metadata.txt index 642c0e5..eeae368 100644 --- a/metadata.txt +++ b/metadata.txt @@ -8,11 +8,15 @@ about=Dockable widget that adds pgRouting layers - pgr_bdDijstra pgr_bdDijstraCost - pgr_KSP version=3.0.2 -qgisMinimumVersion=3.0 +qgisMinimumVersion=3.10 qgisMaximumVersion=3.99 author=Anita Graser, Ko Nagase, Vicky Vergara, Cayetano Benavent, Aasheesh Tiwari email=project@pgrouting.org -changelog=3.0.2 +changelog=4.0.0 + - Works best for pgRouting 4.0.0 + - No longer Support of QGIS < 3.10 + - Using PyQt5 + 3.0.1 - Support for QGIS >= 3.24. - Fixed non-existence database error issue. 3.0.1 @@ -25,7 +29,7 @@ changelog=3.0.2 - Last experimental release works only for QGIS 2.x tags=pgRouting,PostGIS,routing,network analysis icon=icon.png -experimental=False +experimental=True homepage=https://qgis.pgrouting.org/ tracker=https://github.com/pgRouting/pgRoutingLayer/issues repository=https://github.com/pgRouting/pgRoutingLayer diff --git a/pb_tool.cfg b/pb_tool.cfg index 6b15e07..55de234 100644 --- a/pb_tool.cfg +++ b/pb_tool.cfg @@ -1,6 +1,5 @@ [plugin] # Name of the plugin. This is the name of the directory that will -# be created in .qgis2/python/plugins name: pgRoutingLayer # Full path to where you want your plugin directory copied. If empty, @@ -30,13 +29,3 @@ extras: metadata.txt icon.png # Other directories to be deployed with the plugin. # These must be subdirectories under the plugin directory extra_dirs: functions connectors utilities icons - -# ISO code(s) for any locales (translations), separated by spaces. -# Corresponding .ts files must exist in the i18n directory -locales: - -[help] -# the built help directory that should be deployed with the plugin -dir: help/build/html -# the name of the directory to target in the deployed plugin -target: help From 3b77936cec8d10e07c49f4abf9adea74d99ccd35 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Wed, 15 Oct 2025 11:34:18 -0600 Subject: [PATCH 02/10] FunctionBase min pgRouting version 3.0 - Cost Path annotations on the middle of the path - One Path is not used anymore --- functions/FunctionBase.py | 108 ++++++++++++-------------------------- 1 file changed, 34 insertions(+), 74 deletions(-) diff --git a/functions/FunctionBase.py b/functions/FunctionBase.py index 02a0e30..17922fd 100644 --- a/functions/FunctionBase.py +++ b/functions/FunctionBase.py @@ -33,11 +33,12 @@ from psycopg2 import sql from pgRoutingLayer import pgRoutingLayer_utils as Utils +from pgRoutingLayer.utilities import pgr_queries as PgrQ class FunctionBase(object): - minPGRversion = 2.1 + minPGRversion = 3.0 # the mayority of the functions have this values exportButton = True @@ -239,47 +240,21 @@ def drawManyPaths(self, rows, columns, con, args, geomType, canvasItemList, mapC resultPathsRubberBands.append(rubberBand) rubberBand = None - @classmethod - def drawOnePath(self, rows, con, args, geomType, canvasItemList, mapCanvas): - ''' draws line string on the mapCanvas. ''' - resultPathRubberBand = canvasItemList['path'] - for row in rows: - cur2 = con.cursor() - args['result_node_id'] = sql.Literal(row[1]) - args['result_edge_id'] = sql.Literal(row[2]) - args['result_cost'] = row[3] - if row[2] != -1: - query2 = sql.SQL(""" - SELECT ST_AsText({geom_t} FROM {edge_schema}.{edge_table} - WHERE {source} = {result_node_id} AND {id} = {result_edge_id} - UNION - SELECT ST_AsText(ST_Reverse({geom_t}) FROM {edge_schema}.{edge_table} - WHERE {target} = {result_node_id} AND {id} = {result_edge_id}; - """).format(**args) - - cur2.execute(query2) - row2 = cur2.fetchone() - - geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QgsWkbTypes.MultiLineString: - for line in geom.asMultiPolyline(): - for pt in line: - resultPathRubberBand.addPoint(pt) - elif geom.wkbType() == QgsWkbTypes.LineString: - for pt in geom.asPolyline(): - resultPathRubberBand.addPoint(pt) - @classmethod def drawCostPaths(self, rows, con, args, geomType, canvasItemList, mapCanvas): resultPathsRubberBands = canvasItemList['paths'] rubberBand = None cur_path_id = -1 + resultNodesTextAnnotations = canvasItemList['annotations'] for row in rows: - cur2 = con.cursor() + cursor = con.cursor() + midPointCursor = con.cursor() args['result_path_id'] = row[0] args['result_source_id'] = sql.Literal(row[1]) args['result_target_id'] = sql.Literal(row[2]) args['result_cost'] = row[3] + args['result_path_name'] = row[4] + if args['result_path_id'] != cur_path_id: cur_path_id = args['result_path_id'] if rubberBand: @@ -289,19 +264,16 @@ def drawCostPaths(self, rows, con, args, geomType, canvasItemList, mapCanvas): rubberBand = QgsRubberBand(mapCanvas, Utils.getRubberBandType(False)) rubberBand.setColor(QColor(255, 0, 0, 128)) rubberBand.setWidth(4) - if args['result_cost'] != -1: - query2 = sql.SQL(""" - SELECT ST_AsText( ST_MakeLine( - (SELECT {geometry_vt} FROM {vertex_schema}.{vertex_table} WHERE id = {result_source_id}), - (SELECT {geometry_vt} FROM {vertex_schema}.{vertex_table} WHERE id = {result_target_id}) - )) - """).format(**args) - # Utils.logMessage(query2) - cur2.execute(query2) - row2 = cur2.fetchone() - # Utils.logMessage(str(row2[0])) - geom = QgsGeometry().fromWkt(str(row2[0])) + if args['result_cost'] != -1: + costLine = PgrQ.getCostLine(args, sql.Literal(row[1]), sql.Literal(row[2])) + # Utils.logMessage(costLine.as_string(cursor)) + cursor.execute(costLine) + row2 = cursor.fetchone() + line = str(row2[0]) + # Utils.logMessage(line) + + geom = QgsGeometry().fromWkt(line) if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: @@ -310,36 +282,24 @@ def drawCostPaths(self, rows, con, args, geomType, canvasItemList, mapCanvas): for pt in geom.asPolyline(): rubberBand.addPoint(pt) - # TODO label the edge instead of labeling the target points + # Label the edge + midPoint = PgrQ.getMidPoint() + midPointstr = midPoint.as_string(con) + midPointCursor.execute(midPoint,(line,)) + pointRow = midPointCursor.fetchone() + # Utils.logMessage("The point:" + str(pointRow[0])) + + ptgeom = QgsGeometry().fromWkt(str(pointRow[0])) + pt = ptgeom.asPoint() + textDocument = QTextDocument("{0!s}:{1}".format(args['result_path_name'], args['result_cost'])) + textAnnotation = QgsTextAnnotation() + textAnnotation.setMapPosition(pt) + textAnnotation.setFrameSizeMm(QSizeF(20, 5)) + textAnnotation.setFrameOffsetFromReferencePointMm(QPointF(5, -5)) + textAnnotation.setDocument(textDocument) + QgsMapCanvasAnnotationItem(textAnnotation, mapCanvas) + resultNodesTextAnnotations.append(textAnnotation) + if rubberBand: resultPathsRubberBands.append(rubberBand) rubberBand = None - resultNodesTextAnnotations = canvasItemList['annotations'] - for row in rows: - cur2 = con.cursor() - args['result_seq'] = row[0] - args['result_source_id'] = sql.Literal(row[1]) - result_target_id = row[2] - args['result_target_id'] = sql.Literal(result_target_id) - result_cost = row[3] - query2 = sql.SQL(""" - SELECT ST_AsText( ST_startPoint({geometry}) ) FROM {edge_schema}.{edge_table} - WHERE {source} = {result_target_id} - UNION - SELECT ST_AsText( ST_endPoint( {geometry} ) ) FROM {edge_schema}.{edge_table} - WHERE {target} = {result_target_id} - """).format(**args) - cur2.execute(query2) - row2 = cur2.fetchone() - - geom = QgsGeometry().fromWkt(str(row2[0])) - pt = geom.asPoint() - textDocument = QTextDocument("{0!s}:{1}".format(result_target_id, result_cost)) - textAnnotation = QgsTextAnnotation() - textAnnotation.setMapPosition(geom.asPoint()) - textAnnotation.setFrameSize(QSizeF(textDocument.idealWidth(), 20)) - textAnnotation.setFrameOffsetFromReferencePoint(QPointF(20, -40)) - textAnnotation.setDocument(textDocument) - - QgsMapCanvasAnnotationItem(textAnnotation, mapCanvas) - resultNodesTextAnnotations.append(textAnnotation) From 8b03357493b9ee8d02aa6e25086dc96561be7dc4 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Wed, 15 Oct 2025 11:35:21 -0600 Subject: [PATCH 03/10] pgr_KSP min pgRouting version 3.6 - Using the many to many signature - One Path is not used anymore --- functions/pgr_KSP.py | 48 ++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/functions/pgr_KSP.py b/functions/pgr_KSP.py index 92cdcd0..68812d7 100644 --- a/functions/pgr_KSP.py +++ b/functions/pgr_KSP.py @@ -31,11 +31,16 @@ class Function(FunctionBase): - minPGRversion = 2.1 + minPGRversion = 3.6 def __init__(self, ui): FunctionBase.__init__(self, ui) + @classmethod + def isSupportedVersion(self, version): + ''' Checks supported version ''' + return version >= 3.6 + @classmethod def getName(self): ''' returns Function name. ''' @@ -45,57 +50,30 @@ def getName(self): def getControlNames(self, version): ''' returns control names. ''' return self.commonControls + self.commonBoxes + [ - 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', - 'labelTargetId', 'lineEditTargetId', 'buttonSelectTargetId', + 'labelSourceIds', 'lineEditSourceIds', 'buttonSelectSourceIds', + 'labelTargetIds', 'lineEditTargetIds', 'buttonSelectTargetIds', 'labelPaths', 'lineEditPaths', 'checkBoxHeapPaths'] def getQuery(self, args): ''' returns the sql query in required signature format of pgr_KSP ''' return sql.SQL(""" SELECT seq, - '(' || {source_id} || ', ' || {target_id} || ')-' || path_id AS path_name, - path_id AS _path_id, + '(' || start_vid || ',' || end_vid || ')-' || path_id AS path_name, path_seq AS _path_seq, + start_vid AS _start_vid, end_vid AS _end_vid, + path_id AS _path_id, node AS _node, edge AS _edge, cost AS _cost FROM pgr_KSP(' {innerQuery} ', - {source_id}, {target_id}, {Kpaths}, {directed}, {heap_paths}) + {source_ids}, {target_ids}, {Kpaths}, {directed}, {heap_paths}) """).format(**args) def getExportQuery(self, args): return self.getJoinResultWithEdgeTable(args) def getExportMergeQuery(self, args): - args['result_query'] = self.getQuery(args) - return sql.SQL("""WITH - result AS ( {result_query} ), - with_geom AS ( - SELECT seq, result.path_name, - CASE - WHEN result._node = et.{source} - THEN et.{geometry} - ELSE ST_Reverse(et.{geometry}) - END AS path_geom - FROM {edge_schema}.{edge_table} AS et JOIN result ON et.{id} = result._edge - ), - one_geom AS ( - SELECT path_name, ST_LineMerge(ST_Union(path_geom)) AS path_geom - FROM with_geom GROUP BY path_name ORDER BY path_name - ), - aggregates AS ( - SELECT - path_name, _path_id, - SUM(_cost) AS agg_cost, - array_agg(_node ORDER BY _path_seq) AS _nodes, - array_agg(_edge ORDER BY _path_seq) AS _edges - FROM result - GROUP BY path_name, _path_id - ) - SELECT row_number() over() as seq, - _path_id, path_name, _nodes, _edges, agg_cost, path_geom - FROM aggregates JOIN one_geom USING (path_name) ORDER BY _path_id - """).format(**args) + return self.getExportManySourceManyTargetMergeQuery(args) def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): ''' draw the result ''' From 0a1ec971d3d82d7458d925384bfdfeb8cf1152f2 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Wed, 15 Oct 2025 11:37:27 -0600 Subject: [PATCH 04/10] CostBase: Rewrite of the way the query is created --- functions/CostBase.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/functions/CostBase.py b/functions/CostBase.py index a36e399..1992237 100644 --- a/functions/CostBase.py +++ b/functions/CostBase.py @@ -34,12 +34,6 @@ class CostBase(FunctionBase): def __init__(self, ui): FunctionBase.__init__(self, ui) - @classmethod - def isSupportedVersion(self, version): - ''' Checks supported version ''' - # valid starting pgr v2.1 - return version >= 2.1 - @classmethod def canExportMerged(self): return False @@ -67,12 +61,32 @@ def getExportQuery(self, args): return sql.SQL(""" WITH - result AS ( {result_query} ) - SELECT result.*, ST_MakeLine(a.the_geom, b.the_geom) AS path_geom + result AS ( {result_query} ), + departure AS ( + SELECT start_vid, end_vid, ST_startPoint(geom) AS depart + FROM result JOIN {edge_table} ON ({edge_table}.{source} = start_vid) + + UNION + + SELECT start_vid, end_vid, ST_endPoint(geom) + FROM result JOIN {edge_table} ON ({edge_table}.{target} = start_vid) + ), + + destination AS ( + SELECT start_vid, end_vid, ST_startPoint(geom) AS arrive + FROM result JOIN {edge_table} ON ({edge_table}.{source} = end_vid) + + UNION + + SELECT start_vid, end_vid, ST_endPoint(geom) + FROM result JOIN {edge_table} ON ({edge_table}.{target} = end_vid) + ) + + SELECT result.*, ST_MakeLine(depart, arrive) AS path_geom FROM result - JOIN {vertex_schema}.{vertex_table} AS a ON (start_vid = a.id) - JOIN {vertex_schema}.{vertex_table} AS b ON (end_vid = b.id) + JOIN departure USING (start_vid, end_vid) + JOIN destination USING (start_vid, end_vid) """).format(**args) def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): From 84b28d979addb9aacb9873cc791beb2c01db7865 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Wed, 15 Oct 2025 11:39:13 -0600 Subject: [PATCH 05/10] Queries generator: Fix/rewrite of queries --- utilities/pgr_queries.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/utilities/pgr_queries.py b/utilities/pgr_queries.py index 49210e6..b7a3849 100644 --- a/utilities/pgr_queries.py +++ b/utilities/pgr_queries.py @@ -71,6 +71,24 @@ def getEdgesQueryXY(args): FROM {edge_schema}.{edge_table} {where_clause} """.replace("\\n", r"\n")).format(**args) +def getEndPoint(args, vertex_id): + return sql.SQL(""" + SELECT ST_startPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {source} = {vid} + UNION + SELECT ST_endPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {target} = {vid} + """.replace("\\n", r"\n")).format(**args, vid=vertex_id) + +def getCostLine(args, departure, arrival): + return sql.Composed([ + sql.SQL("SELECT ST_asText(ST_makeLine(("), + getEndPoint(args, departure), + sql.SQL("), ("), + getEndPoint(args, arrival), + sql.SQL("))) AS line") + ]) + +def getMidPoint(): + return sql.SQL("SELECT ST_asText(ST_LineInterpolatePoint(ST_GeomFromText(%s),0.5))") def get_closestVertexInfo(args): return sql.SQL(""" From db7c860d75ad02d8107bff365a99425f48d56609 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Wed, 15 Oct 2025 11:43:41 -0600 Subject: [PATCH 06/10] Cleanup, changing requirements and small rewrites to adjust to previous changes --- pgRoutingLayer.py | 55 +++++++++++++++-------------------------- pgRoutingLayer_utils.py | 2 +- requirements.txt | 1 + 3 files changed, 22 insertions(+), 36 deletions(-) diff --git a/pgRoutingLayer.py b/pgRoutingLayer.py index d4e1906..c6f0977 100644 --- a/pgRoutingLayer.py +++ b/pgRoutingLayer.py @@ -954,8 +954,7 @@ def get_innerQueryArguments(self, controls): if not self.dock.checkBoxHasReverseCost.isChecked(): args['reverse_cost'] = sql.SQL(" -1 ") else: - args['reverse_cost'] = sql.SQL("{}").format(sql.Identifier( - str(self.dock.lineEditReverseCost.text()))) + args['reverse_cost'] = sql.Identifier(str(self.dock.lineEditReverseCost.text())) if 'lineEditX1' in controls: args['x1'] = sql.Identifier(self.dock.lineEditX1.text()) @@ -1031,33 +1030,17 @@ def _getArguments(self, controls, conn): function = str(self.dock.comboBoxFunction.currentText()).lower() args['function'] = sql.Identifier(str(function)) - if function in ['pgr_astarcost', 'pgr_dijkstracost', 'pgr_bdastarcost', 'pgr_bddijkstracost']: - # TODO: capture vertices table, geometry of vertices table - args['vertex_schema'] = sql.Identifier(str(self.dock.lineEditSchema.text())) - args['vertex_table'] = sql.Identifier(str(self.dock.lineEditTable.text()) + '_vertices_pgr') - args['geometry_vt'] = sql.Identifier(str(self.dock.lineEditGeometry.text())) - # QMessageBox.information(self.dock, self.dock.windowTitle(), - # 'TODO: capture vertices table, geometry of vertices table, label the edges') - if 'lineEditX1' in controls: args['astarHeuristic'] = sql.Literal(str(self.dock.selectAstarHeuristic.currentIndex())) args['astarFactor'] = sql.Literal(str(self.dock.selectAstarFactor.text())) args['astarEpsilon'] = sql.Literal(str(self.dock.selectAstarEpsilon.value())) - # args['rule'] = self.dock.lineEditRule.text() if 'lineEditRule' in controls - # args['to_cost'] = self.dock.lineEditToCost.text() if 'lineEditToCost' in controls: - if 'lineEditIds' in controls: args['ids'] = self.dock.lineEditIds.text() if 'lineEditPcts' in controls: args['pcts'] = self.dock.lineEditPcts.text() - # Used in pgr_KSP - if 'lineEditSourceId' in controls: - args['source_id'] = sql.Literal(self.dock.lineEditSourceId.text()) - args['target_id'] = sql.Literal(self.dock.lineEditTargetId.text()) - # Used in pgr_KSP if 'lineEditPaths' in controls: args['Kpaths'] = sql.Literal(self.dock.lineEditPaths.text()) @@ -1085,13 +1068,6 @@ def _getArguments(self, controls, conn): if 'checkBoxHeapPaths' in controls: args['heap_paths'] = sql.SQL("heap_paths := {}::BOOLEAN").format(sql.Literal(str(self.dock.checkBoxHeapPaths.isChecked()).lower())) - # if 'labelDrivingSide' in controls: - # args['driving_side'] = str('b') - # if (self.dock.checkBoxLeft.isChecked() == True and self.dock.checkBoxRight.isChecked() == False): - # args['driving_side'] = str('l') - # elif (self.dock.checkBoxLeft.isChecked() == False and self.dock.checkBoxRight.isChecked() == True): - # args['driving_side'] = str('r') - return args # emulate "matching.sql" - "find_nearest_node_within_distance" @@ -1113,13 +1089,25 @@ def findNearestNode(self, args, pt): args['SBBOX'] = self.getBBOX(args['srid'])[0] args['geom_t'] = Utils.getTransformedGeom(args['srid'], args['dbcanvas_srid'], args['geometry']) - db, cur = self._exec_sql(PgrQ.get_closestVertexInfo(args)) - if cur: - row = cur.fetchone() - db.con.close() - return True, row[0], row[2] - else: - return False, None, None + try: + dbname = str(self.dock.comboConnections.currentText()) + db = self.actionsDb[dbname].connect() + connection = db.con + cursor = connection.cursor() + + #Utils.logMessage(PgrQ.get_closestVertexInfo(args).as_string(connection)) + cursor.execute(PgrQ.get_closestVertexInfo(args)) + if cursor: + data = cursor.fetchone() + return True, data[0], data[2] + else: + return False, None, None + + db.connection.close() + + except psycopg2.DatabaseError as e: + QApplication.restoreOverrideCursor() + QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) # emulate "matching.sql" - "find_nearest_link_within_distance" def findNearestLink(self, args, pt): @@ -1182,9 +1170,6 @@ def loadSettings(self): self.dock.selectAstarFactor.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/factor', '1')) self.dock.selectAstarEpsilon.setTickPosition(int(Utils.getStringValue(settings, '/pgRoutingLayer/sql/epsilon', '100'))) - # self.dock.lineEditRule.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/rule', 'rule')) - # self.dock.lineEditToCost.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/to_cost', 'to_cost')) - self.dock.lineEditIds.setText(Utils.getStringValue(settings, '/pgRoutingLayer/ids', '')) self.dock.lineEditPcts.setText(Utils.getStringValue(settings, '/pgRoutingLayer/pcts', '')) diff --git a/pgRoutingLayer_utils.py b/pgRoutingLayer_utils.py index aabc922..0c7383f 100644 --- a/pgRoutingLayer_utils.py +++ b/pgRoutingLayer_utils.py @@ -94,7 +94,7 @@ def getCanvasSrid(crs): def createFromSrid(crs, srid): ''' Creates Spatial reference system based of SRID. ''' - return crs.createFromSrid(srid) + return crs.createFromProj(str(srid)) def getRubberBandType(isPolygon): diff --git a/requirements.txt b/requirements.txt index 504add7..e8b567a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ mkdocs pb_tool +setuptools From 7f7eab945e55665279b1208987bf2fccdcea27f1 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Wed, 15 Oct 2025 13:09:52 -0600 Subject: [PATCH 07/10] Making the pgr_KSP and DijkstraBase as similar as possible --- functions/DijkstraBase.py | 11 +++++------ functions/FunctionBase.py | 1 - functions/pgr_KSP.py | 19 ++++++++----------- tests/qgis_models.py | 2 +- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/functions/DijkstraBase.py b/functions/DijkstraBase.py index f48fb03..14713bd 100644 --- a/functions/DijkstraBase.py +++ b/functions/DijkstraBase.py @@ -51,13 +51,12 @@ def prepare(self, canvasItemList): @classmethod def getQuery(self, args): ''' returns the sql query in required signature format of pgr_dijkstra ''' - return sql.SQL(""" - SELECT seq, '(' || start_vid || ',' || end_vid || ')' AS path_name, + return sql.SQL("""SELECT seq, + '(' || start_vid || ',' || end_vid || ')' AS path_name, path_seq AS _path_seq, start_vid AS _start_vid, end_vid AS _end_vid, - node AS _node, edge AS _edge, cost AS _cost, lead(agg_cost) over() AS _agg_cost - FROM {function}(' - {innerQuery} - ', + node AS _node, edge AS _edge, + cost AS _cost, agg_cost AS _agg_cost + FROM {function}('{innerQuery}', {source_ids}, {target_ids}, {directed}) """).format(**args) diff --git a/functions/FunctionBase.py b/functions/FunctionBase.py index 17922fd..266db68 100644 --- a/functions/FunctionBase.py +++ b/functions/FunctionBase.py @@ -264,7 +264,6 @@ def drawCostPaths(self, rows, con, args, geomType, canvasItemList, mapCanvas): rubberBand = QgsRubberBand(mapCanvas, Utils.getRubberBandType(False)) rubberBand.setColor(QColor(255, 0, 0, 128)) rubberBand.setWidth(4) - if args['result_cost'] != -1: costLine = PgrQ.getCostLine(args, sql.Literal(row[1]), sql.Literal(row[2])) # Utils.logMessage(costLine.as_string(cursor)) diff --git a/functions/pgr_KSP.py b/functions/pgr_KSP.py index 68812d7..5763aaa 100644 --- a/functions/pgr_KSP.py +++ b/functions/pgr_KSP.py @@ -56,16 +56,13 @@ def getControlNames(self, version): def getQuery(self, args): ''' returns the sql query in required signature format of pgr_KSP ''' - return sql.SQL(""" - SELECT seq, - '(' || start_vid || ',' || end_vid || ')-' || path_id AS path_name, - path_seq AS _path_seq, - start_vid AS _start_vid, end_vid AS _end_vid, - path_id AS _path_id, - node AS _node, - edge AS _edge, - cost AS _cost - FROM pgr_KSP(' {innerQuery} ', + return sql.SQL("""SELECT seq, + '(' || start_vid || ',' || end_vid || ')-' || path_id AS path_name, + path_seq AS _path_seq, start_vid AS _start_vid, end_vid AS _end_vid, + node AS _node, edge AS _edge, + path_id AS _path_id, + cost AS _cost, agg_cost AS _agg_cost + FROM pgr_KSP('{innerQuery}', {source_ids}, {target_ids}, {Kpaths}, {directed}, {heap_paths}) """).format(**args) @@ -77,5 +74,5 @@ def getExportMergeQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): ''' draw the result ''' - columns = [2, 4, 5] + columns = [2, 5, 6] self.drawManyPaths(rows, columns, con, args, geomType, canvasItemList, mapCanvas) diff --git a/tests/qgis_models.py b/tests/qgis_models.py index 534525b..f10c4d3 100755 --- a/tests/qgis_models.py +++ b/tests/qgis_models.py @@ -29,7 +29,7 @@ import logging import sys -from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QCoreApplication +from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QCoreApplication from qgis.core import QgsMapLayerRegistry, QgsApplication, QgsVectorLayer from qgis.gui import QgsMapCanvasLayer import config From 438c4b3f11fafd8768eee2e23322c106c6686cb7 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Wed, 15 Oct 2025 14:01:46 -0600 Subject: [PATCH 08/10] Fixing/Using issues found by rabbit --- functions/CostBase.py | 41 +++++++++++++++++++-------------- functions/FunctionBase.py | 1 - functions/pgr_aStar.py | 2 -- functions/pgr_aStarCost.py | 2 -- functions/pgr_bdAstar.py | 2 -- functions/pgr_bdAstarCost.py | 2 -- functions/pgr_bdDijkstra.py | 2 -- functions/pgr_bdDijkstraCost.py | 2 -- functions/pgr_dijkstra.py | 2 -- functions/pgr_dijkstraCost.py | 2 -- metadata.txt | 6 ++--- pgRoutingLayer.py | 10 ++++++-- pgRoutingLayer_utils.py | 3 +-- utilities/pgr_queries.py | 11 +++++---- 14 files changed, 43 insertions(+), 45 deletions(-) diff --git a/functions/CostBase.py b/functions/CostBase.py index 1992237..1360f08 100644 --- a/functions/CostBase.py +++ b/functions/CostBase.py @@ -63,24 +63,31 @@ def getExportQuery(self, args): WITH result AS ( {result_query} ), departure AS ( - SELECT start_vid, end_vid, ST_startPoint(geom) AS depart - FROM result JOIN {edge_table} ON ({edge_table}.{source} = start_vid) - - UNION - - SELECT start_vid, end_vid, ST_endPoint(geom) - FROM result JOIN {edge_table} ON ({edge_table}.{target} = start_vid) - ), - + SELECT DISTINCT ON (start_vid, end_vid) + start_vid, end_vid, + CASE + WHEN {edge_table}.{source} = start_vid + THEN ST_StartPoint({edge_table}.{geometry}) + ELSE ST_EndPoint({edge_table}.{geometry}) + END AS depart + FROM result + JOIN {edge_schema}.{edge_table} + ON ({edge_table}.{source} = start_vid OR {edge_table}.{target} = start_vid) + ORDER BY start_vid, end_vid, {edge_table}.{id} + ), destination AS ( - SELECT start_vid, end_vid, ST_startPoint(geom) AS arrive - FROM result JOIN {edge_table} ON ({edge_table}.{source} = end_vid) - - UNION - - SELECT start_vid, end_vid, ST_endPoint(geom) - FROM result JOIN {edge_table} ON ({edge_table}.{target} = end_vid) - ) + SELECT DISTINCT ON (start_vid, end_vid) + start_vid, end_vid, + CASE + WHEN {edge_table}.{source} = end_vid + THEN ST_StartPoint({edge_table}.{geometry}) + ELSE ST_EndPoint({edge_table}.{geometry}) + END AS arrive + FROM result + JOIN {edge_schema}.{edge_table} + ON ({edge_table}.{source} = end_vid OR {edge_table}.{target} = end_vid) + ORDER BY start_vid, end_vid, {edge_table}.{id} + ) SELECT result.*, ST_MakeLine(depart, arrive) AS path_geom diff --git a/functions/FunctionBase.py b/functions/FunctionBase.py index 266db68..49e436c 100644 --- a/functions/FunctionBase.py +++ b/functions/FunctionBase.py @@ -283,7 +283,6 @@ def drawCostPaths(self, rows, con, args, geomType, canvasItemList, mapCanvas): # Label the edge midPoint = PgrQ.getMidPoint() - midPointstr = midPoint.as_string(con) midPointCursor.execute(midPoint,(line,)) pointRow = midPointCursor.fetchone() # Utils.logMessage("The point:" + str(pointRow[0])) diff --git a/functions/pgr_aStar.py b/functions/pgr_aStar.py index 9b3c3f5..1034d2c 100644 --- a/functions/pgr_aStar.py +++ b/functions/pgr_aStar.py @@ -30,8 +30,6 @@ class Function(AstarBase): - minPGRversion = 2.4 - def __init__(self, ui): AstarBase.__init__(self, ui) diff --git a/functions/pgr_aStarCost.py b/functions/pgr_aStarCost.py index bb7f9a5..304f2f6 100644 --- a/functions/pgr_aStarCost.py +++ b/functions/pgr_aStarCost.py @@ -30,8 +30,6 @@ class Function(CostBase): - minPGRversion = 2.4 - def __init__(self, ui): CostBase.__init__(self, ui) diff --git a/functions/pgr_bdAstar.py b/functions/pgr_bdAstar.py index 90e1466..6509d72 100644 --- a/functions/pgr_bdAstar.py +++ b/functions/pgr_bdAstar.py @@ -30,8 +30,6 @@ class Function(AstarBase): - minPGRversion = 2.5 - def __init__(self, ui): AstarBase.__init__(self, ui) diff --git a/functions/pgr_bdAstarCost.py b/functions/pgr_bdAstarCost.py index 31fb3ec..b4ff50d 100644 --- a/functions/pgr_bdAstarCost.py +++ b/functions/pgr_bdAstarCost.py @@ -30,8 +30,6 @@ class Function(CostBase): - minPGRversion = 2.5 - def __init__(self, ui): CostBase.__init__(self, ui) diff --git a/functions/pgr_bdDijkstra.py b/functions/pgr_bdDijkstra.py index 6465ec5..56f96f6 100644 --- a/functions/pgr_bdDijkstra.py +++ b/functions/pgr_bdDijkstra.py @@ -29,8 +29,6 @@ class Function(DijkstraBase): - minPGRversion = 2.5 - def __init__(self, ui): DijkstraBase.__init__(self, ui) diff --git a/functions/pgr_bdDijkstraCost.py b/functions/pgr_bdDijkstraCost.py index dbc05a9..0db76fb 100644 --- a/functions/pgr_bdDijkstraCost.py +++ b/functions/pgr_bdDijkstraCost.py @@ -30,8 +30,6 @@ class Function(CostBase): - minPGRversion = 2.5 - def __init__(self, ui): CostBase.__init__(self, ui) diff --git a/functions/pgr_dijkstra.py b/functions/pgr_dijkstra.py index 7892a44..b429541 100644 --- a/functions/pgr_dijkstra.py +++ b/functions/pgr_dijkstra.py @@ -29,8 +29,6 @@ class Function(DijkstraBase): - minPGRversion = 2.1 - def __init__(self, ui): DijkstraBase.__init__(self, ui) diff --git a/functions/pgr_dijkstraCost.py b/functions/pgr_dijkstraCost.py index 540811b..2565708 100644 --- a/functions/pgr_dijkstraCost.py +++ b/functions/pgr_dijkstraCost.py @@ -30,8 +30,6 @@ class Function(CostBase): - minPGRversion = 2.2 - def __init__(self, ui): CostBase.__init__(self, ui) diff --git a/metadata.txt b/metadata.txt index eeae368..d8583bf 100644 --- a/metadata.txt +++ b/metadata.txt @@ -7,7 +7,7 @@ about=Dockable widget that adds pgRouting layers - pgr_dijstra pgr_dijstraCost - pgr_bdDijstra pgr_bdDijstraCost - pgr_KSP -version=3.0.2 +version=4.0.0 qgisMinimumVersion=3.10 qgisMaximumVersion=3.99 author=Anita Graser, Ko Nagase, Vicky Vergara, Cayetano Benavent, Aasheesh Tiwari @@ -16,7 +16,7 @@ changelog=4.0.0 - Works best for pgRouting 4.0.0 - No longer Support of QGIS < 3.10 - Using PyQt5 - 3.0.1 + 3.0.2 - Support for QGIS >= 3.24. - Fixed non-existence database error issue. 3.0.1 @@ -29,7 +29,7 @@ changelog=4.0.0 - Last experimental release works only for QGIS 2.x tags=pgRouting,PostGIS,routing,network analysis icon=icon.png -experimental=True +experimental=False homepage=https://qgis.pgrouting.org/ tracker=https://github.com/pgRouting/pgRoutingLayer/issues repository=https://github.com/pgRouting/pgRoutingLayer diff --git a/pgRoutingLayer.py b/pgRoutingLayer.py index c6f0977..efe59ee 100644 --- a/pgRoutingLayer.py +++ b/pgRoutingLayer.py @@ -1089,6 +1089,7 @@ def findNearestNode(self, args, pt): args['SBBOX'] = self.getBBOX(args['srid'])[0] args['geom_t'] = Utils.getTransformedGeom(args['srid'], args['dbcanvas_srid'], args['geometry']) + db = None try: dbname = str(self.dock.comboConnections.currentText()) db = self.actionsDb[dbname].connect() @@ -1103,12 +1104,17 @@ def findNearestNode(self, args, pt): else: return False, None, None - db.connection.close() - except psycopg2.DatabaseError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) + finally: + if db and db.con: + try: + db.con.close() + except Exception: + pass + # emulate "matching.sql" - "find_nearest_link_within_distance" def findNearestLink(self, args, pt): ''' finds the nearest link to selected point ''' diff --git a/pgRoutingLayer_utils.py b/pgRoutingLayer_utils.py index 0c7383f..63ba4e0 100644 --- a/pgRoutingLayer_utils.py +++ b/pgRoutingLayer_utils.py @@ -94,8 +94,7 @@ def getCanvasSrid(crs): def createFromSrid(crs, srid): ''' Creates Spatial reference system based of SRID. ''' - return crs.createFromProj(str(srid)) - + return crs.createFromString(f"EPSG:{srid}") def getRubberBandType(isPolygon): ''' returns RubberBandType as polygon or lineString ''' diff --git a/utilities/pgr_queries.py b/utilities/pgr_queries.py index b7a3849..21e3659 100644 --- a/utilities/pgr_queries.py +++ b/utilities/pgr_queries.py @@ -72,11 +72,14 @@ def getEdgesQueryXY(args): """.replace("\\n", r"\n")).format(**args) def getEndPoint(args, vertex_id): +def getEndPoint(args, vertex_id): + # Prefer source endpoint when available; otherwise fallback to target endpoint return sql.SQL(""" - SELECT ST_startPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {source} = {vid} - UNION - SELECT ST_endPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {target} = {vid} - """.replace("\\n", r"\n")).format(**args, vid=vertex_id) + SELECT COALESCE( + (SELECT ST_StartPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {source} = {vid} LIMIT 1), + (SELECT ST_EndPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {target} = {vid} LIMIT 1) + ) + """.replace("\\n", r"\n")).format(**args, vid=vertex_id) def getCostLine(args, departure, arrival): return sql.Composed([ From d96bb5fbe528bd8ed32fd25798290286b5fc5979 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Fri, 17 Oct 2025 11:25:00 -0600 Subject: [PATCH 09/10] drivingDistance.py removing unused code Needs 100% rewrite so better to start over --- functions/drivingDistance.py | 164 ----------------------------------- 1 file changed, 164 deletions(-) delete mode 100644 functions/drivingDistance.py diff --git a/functions/drivingDistance.py b/functions/drivingDistance.py deleted file mode 100644 index a727217..0000000 --- a/functions/drivingDistance.py +++ /dev/null @@ -1,164 +0,0 @@ -# -*- coding: utf-8 -*- -# /*PGR-GNU***************************************************************** -# File: drivingDistance.py -# -# Copyright (c) 2011~2019 pgRouting developers -# Mail: project@pgrouting.org -# -# Developer's GitHub nickname: -# - cayetanobv -# - AasheeshT -# - sanak -# - cvvergara -# - anitagraser -# ------ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# ********************************************************************PGR-GNU*/ - -from __future__ import absolute_import -from qgis.PyQt.QtCore import Qt -from qgis.PyQt.QtGui import * -from qgis.core import QgsGeometry, QgsPointXY -from qgis.gui import QgsVertexMarker -from psycopg2 import sql -from pgRoutingLayer import pgRoutingLayer_utils as Utils -from .FunctionBase import FunctionBase - - -class Function(FunctionBase): - # TODO fix completely - - @classmethod - def getName(self): - ''' returns Function name. ''' - return 'pgr_drivingDistance' - - @classmethod - def getControlNames(self, version): - ''' returns control names. ''' - if version < 2.1: - # version 2.0 has only one to one - return self.commonControls + self.commonBoxes + [ - 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', - 'labelDistance', 'lineEditDistance', - ] - else: - return self.commonControls + self.commonBoxes + [ - 'labelSourceIds', 'lineEditSourceIds', 'buttonSelectSourceIds', - 'labelDistance', 'lineEditDistance', - ] - - def prepare(self, canvasItemList): - resultNodesVertexMarkers = canvasItemList['markers'] - for marker in resultNodesVertexMarkers: - marker.setVisible(False) - canvasItemList['markers'] = [] - - def getQuery(self, args): - ''' returns the sql query in required signature format of pgr_drivingDistance ''' - args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) - if (args['version'] < 2.1): - return sql.SQL(""" - SELECT seq, id1 AS _node, id2 AS _edge, cost AS _cost - FROM pgr_drivingDistance(' - SELECT %(id)s::int4 AS id, - %(source)s::int4 AS source, - %(target)s::int4 AS target, - %(cost)s::float8 AS cost%(reverse_cost)s - FROM %(edge_table)s - %(where_clause)s', - %(source_id)s, %(distance)s, - %(directed)s, %(has_reverse_cost)s)""").format(**args) - - # 2.1 or greater - # TODO add equicost flag to gui - return sql.SQL(""" - SELECT seq, '(' || from_v || ', %(distance)s)' AS path_name, - from_v AS _from_v, - node AS _node, edge AS _edge, - cost AS _cost, agg_cost as _agg_cost - FROM pgr_drivingDistance(' - SELECT %(id)s AS id, - %(source)s AS source, - %(target)s AS target, - %(cost)s AS cost%(reverse_cost)s - FROM %(edge_table)s - %(where_clause)s', - ARRAY[%(source_ids)s]::BIGINT[], %(distance)s, - %(directed)s, false) - """).format(**args) - - def getExportQuery(self, args): - # points are returned - args['result_query'] = self.getQuery(args) - - args['with_geom_query'] = sql.SQL(""" - SELECT result.*, - ST_X(the_geom) AS x, ST_Y(the_geom) AS y, - the_geom AS path_geom - FROM %(edge_table)s_vertices_pgr JOIN result - ON %(edge_table)s_vertices_pgr.id = result._node - """).format(**args) - - msgQuery = sql.SQL("""WITH - result AS ( %(result_query)s ), - with_geom AS ( %(with_geom_query)s ) - SELECT with_geom.* - FROM with_geom - ORDER BY seq - """).format(**args) - return msgQuery - - def getExportMergeQuery(self, args): - # the set of edges of the spanning tree are returned - return self.getJoinResultWithEdgeTable(args) - - def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): - ''' draw the result ''' - resultNodesVertexMarkers = canvasItemList['markers'] - schema = """%(edge_schema)s""" % args - table = """%(edge_table)s_vertices_pgr""" % args - srid, geomType = Utils.getSridAndGeomType(con, schema, table, 'the_geom') - Utils.setTransformQuotes(args, srid, args['canvas_srid']) - - for row in rows: - cur2 = con.cursor() - if args['version'] < 2.1: - args['result_node_id'] = row[1] - args['result_edge_id'] = row[2] - args['result_cost'] = row[3] - else: - args['result_node_id'] = row[3] - args['result_edge_id'] = row[4] - args['result_cost'] = row[5] - - query2 = sql.SQL(""" - SELECT ST_AsText(%(transform_s)s the_geom %(transform_e)s) - FROM %(edge_table)s_vertices_pgr - WHERE id = %(result_node_id)d - """).format(**args) - cur2.execute(query2) - row2 = cur2.fetchone() - if (row2): - geom = QgsGeometry().fromWkt(str(row2[0])) - pt = geom.asPoint() - vertexMarker = QgsVertexMarker(mapCanvas) - vertexMarker.setColor(Qt.red) - vertexMarker.setPenWidth(2) - vertexMarker.setIconSize(5) - vertexMarker.setCenter(QgsPointXY(pt)) - resultNodesVertexMarkers.append(vertexMarker) - - def __init__(self, ui): - FunctionBase.__init__(self, ui) From f3e39bad137047b838d050776c3cb64668924681 Mon Sep 17 00:00:00 2001 From: cvvergara Date: Fri, 17 Oct 2025 11:56:40 -0600 Subject: [PATCH 10/10] Using suggestions from rabbit --- help/source/conf.py | 2 +- pgRoutingLayer.py | 6 ++++-- utilities/pgr_queries.py | 21 ++++++++++++--------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/help/source/conf.py b/help/source/conf.py index 1beaeb0..02449d8 100644 --- a/help/source/conf.py +++ b/help/source/conf.py @@ -48,7 +48,7 @@ # The short X.Y version. version = '4.0' # The full version, including alpha/beta/rc tags. -release = '4.0' +release = '4.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pgRoutingLayer.py b/pgRoutingLayer.py index efe59ee..3c8e0c5 100644 --- a/pgRoutingLayer.py +++ b/pgRoutingLayer.py @@ -1098,8 +1098,9 @@ def findNearestNode(self, args, pt): #Utils.logMessage(PgrQ.get_closestVertexInfo(args).as_string(connection)) cursor.execute(PgrQ.get_closestVertexInfo(args)) - if cursor: - data = cursor.fetchone() + data = cursor.fetchone() + db.con.close() + if data: return True, data[0], data[2] else: return False, None, None @@ -1107,6 +1108,7 @@ def findNearestNode(self, args, pt): except psycopg2.DatabaseError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) + return False, None, None finally: if db and db.con: diff --git a/utilities/pgr_queries.py b/utilities/pgr_queries.py index 21e3659..3b24e73 100644 --- a/utilities/pgr_queries.py +++ b/utilities/pgr_queries.py @@ -71,24 +71,27 @@ def getEdgesQueryXY(args): FROM {edge_schema}.{edge_table} {where_clause} """.replace("\\n", r"\n")).format(**args) -def getEndPoint(args, vertex_id): def getEndPoint(args, vertex_id): # Prefer source endpoint when available; otherwise fallback to target endpoint return sql.SQL(""" SELECT COALESCE( - (SELECT ST_StartPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {source} = {vid} LIMIT 1), - (SELECT ST_EndPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {target} = {vid} LIMIT 1) + (SELECT ST_StartPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {source} = {vid} ORDER BY {id} LIMIT 1), + (SELECT ST_EndPoint({geometry}) FROM {edge_schema}.{edge_table} WHERE {target} = {vid} ORDER BY {id} LIMIT 1) ) """.replace("\\n", r"\n")).format(**args, vid=vertex_id) def getCostLine(args, departure, arrival): return sql.Composed([ - sql.SQL("SELECT ST_asText(ST_makeLine(("), - getEndPoint(args, departure), - sql.SQL("), ("), - getEndPoint(args, arrival), - sql.SQL("))) AS line") - ]) + sql.SQL("SELECT ST_AsText("), + args['transform_s'], + sql.SQL("ST_MakeLine(("), + getEndPoint(args, departure), + sql.SQL("), ("), + getEndPoint(args, arrival), + sql.SQL("))"), + args['transform_e'], + sql.SQL(") AS line"), + ]) def getMidPoint(): return sql.SQL("SELECT ST_asText(ST_LineInterpolatePoint(ST_GeomFromText(%s),0.5))")