diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b1e512c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 + +# Docstrings and comments use max_line_length = 79 +[*.py] +max_line_length = 119 + +# Makefiles always use tabs for indentation +[Makefile] +indent_style = tab diff --git a/Test_utils.py b/Test_utils.py deleted file mode 100644 index 4943bcf..0000000 --- a/Test_utils.py +++ /dev/null @@ -1,140 +0,0 @@ -import pgRoutingLayer_utils as utils -import unittest -import sys -from qgis.core import * -from qgis.gui import * -from PyQt4.QtCore import * -from PyQt4.QtGui import * -import psycopg2 -import sip - - - -class TestUtils(unittest.TestCase): - - def test_setStartPoint_1(self): - args = { 'geometry': 'test_geom','table' : 'test_table'} - geomType = 'ST_LineString' - utils.setStartPoint(geomType,args) - self.assertEqual(args['startpoint'], 'ST_StartPoint(test_geom)') - - def test_setStartPoint_2(self): - args = { 'geometry': 'test_geom','table' : 'test_table'} - geomType = 'ST_MultiLineString' - utils.setStartPoint(geomType,args) - self.assertEqual(args['startpoint'], 'ST_StartPoint(ST_GeometryN(test_geom, 1))') - - def test_setEndPoint_1(self): - args = { 'geometry': 'test_geom','table' : 'test_table'} - geomType = 'ST_LineString' - utils.setEndPoint(geomType,args) - self.assertEqual(args['endpoint'], 'ST_EndPoint(test_geom)') - - def test_setEndPoint_2(self): - args = { 'geometry': 'test_geom','table' : 'test_table'} - geomType = 'ST_MultiLineString' - utils.setEndPoint(geomType,args) - self.assertEqual(args['endpoint'], 'ST_EndPoint(ST_GeometryN(test_geom, 1))') - - def test_isSIPv2(self): - self.assertTrue(utils.isSIPv2()) - - def test_getStringValue(self): - setting = QSettings() - setting.setValue('/pgRoutingLayer/Database', 99) - self.assertEqual(utils.getStringValue(setting,'/pgRoutingLayer/Database', 99) ,'99') - - def test_getBoolValue(self): - setting = QSettings() - setting.setValue('/pgRoutingLayer/Database', 99) - self.assertTrue(utils.getBoolValue(setting,'/pgRoutingLayer/Database', 99)) - - - def test_getDestinationCrs(self): - app = QApplication(sys.argv) - # create a map canvas widget - canvas = QgsMapCanvas() - canvas.setCanvasColor(QColor('white')) - canvas.enableAntiAliasing(True) - canvas.setMinimumSize(800, 600) - # load a shapefile - layer = QgsVectorLayer('test_data' ,'poly', 'ogr') - # add the layer to the canvas and zoom to it - QgsMapLayerRegistry.instance().addMapLayer(layer) - canvas.setLayerSet([QgsMapCanvasLayer(layer)]) - canvas.setExtent(layer.extent()) - self.assertIsNotNone(utils.getDestinationCrs(canvas)) - - def test_getCanvasSrid(self): - crs = QgsCoordinateReferenceSystem() - crs.createFromProj4("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs") - self.assertEqual(utils.getCanvasSrid(crs) ,0) - - def test_getRubberBandType_1(self): - isPolygon = True - self.assertEqual(utils.getRubberBandType(isPolygon),2) - - def test_getRubberBandType_2(self): - isPolygon = False - self.assertEqual(utils.getRubberBandType(isPolygon),1) - - def test_getNodeQuery(self): - args = {'geometry' : 'test_geom','source': 1,'startpoint' : 10,'edge_table':'test_Table','target':100,'endpoint':90} - expected_sql= """ - WITH node AS ( - SELECT id::int4, - ST_X(test_geom) AS x, - ST_Y(test_geom) AS y, - test_geom - FROM ( - SELECT 1::int4 AS id, - ST_StartPoint(ST_GeometryN(test_geom, 1)) AS test_geom - FROM test_Table - UNION - SELECT 100::int4 AS id, - ST_EndPoint(ST_GeometryN(test_geom, 1)) AS test_geom - FROM test_Table - ) AS node - )""" - self.maxDiff = None - geomType = 'ST_MultiLineString' - self.assertMultiLineEqual(utils.getNodeQuery(args,geomType),expected_sql) - -if __name__ == '__main__': - unittest.main() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/__init__.py b/__init__.py old mode 100644 new mode 100755 index c0d0b49..bf7b898 --- a/__init__.py +++ b/__init__.py @@ -2,8 +2,8 @@ /*************************************************************************** pgRouting Layer a QGIS plugin - - based on "Fast SQL Layer" plugin Copyright 2011 Pablo Torres Carreira + + based on "Fast SQL Layer" plugin Copyright 2011 Pablo Torres Carreira ------------------- begin : 2011-11-25 copyright : (c) 2011 by Anita Graser @@ -21,6 +21,7 @@ This script initializes the plugin, making it known to QGIS. """ + def name(): return "pgRouting Layer" def description(): @@ -32,5 +33,5 @@ def icon(): def qgisMinimumVersion(): return "1.7" def classFactory(iface): - from pgRoutingLayer import pgRoutingLayer - return pgRoutingLayer(iface) + from pgRoutingLayer.pgRoutingLayer import PgRoutingLayer + return PgRoutingLayer(iface) diff --git a/connectors/__init__.py b/connectors/__init__.py old mode 100644 new mode 100755 diff --git a/connectors/postgis.py b/connectors/postgis.py index 1b9c9e9..47c7313 100755 --- a/connectors/postgis.py +++ b/connectors/postgis.py @@ -38,831 +38,830 @@ class TableAttribute(DbConn.TableAttribute): - def __init__(self, row): - self.num, self.name, self.data_type, self.char_max_len, self.modifier, self.notnull, self.hasdefault, self.default = row + def __init__(self, row): + self.num, self.name, self.data_type, self.char_max_len, self.modifier, self.notnull, self.hasdefault, self.default = row class TableConstraint(DbConn.TableConstraint): - """ class that represents a constraint of a table (relation) """ - - def __init__(self, row): - self.name, con_type, self.is_defferable, self.is_deffered, keys = row[:5] - self.keys = list(map(int, keys.split(' '))) - self.con_type = TableConstraint.types[con_type] # convert to enum - if self.con_type == TableConstraint.TypeCheck: - self.check_src = row[5] - elif self.con_type == TableConstraint.TypeForeignKey: - self.foreign_table = row[6] - self.foreign_on_update = TableConstraint.on_action[row[7]] - self.foreign_on_delete = TableConstraint.on_action[row[8]] - self.foreign_match_type = TableConstraint.match_types[row[9]] - self.foreign_keys = row[10] + """ class that represents a constraint of a table (relation) """ + + def __init__(self, row): + self.name, con_type, self.is_defferable, self.is_deffered, keys = row[:5] + self.keys = list(map(int, keys.split(' '))) + self.con_type = TableConstraint.types[con_type] # convert to enum + if self.con_type == TableConstraint.TypeCheck: + self.check_src = row[5] + elif self.con_type == TableConstraint.TypeForeignKey: + self.foreign_table = row[6] + self.foreign_on_update = TableConstraint.on_action[row[7]] + self.foreign_on_delete = TableConstraint.on_action[row[8]] + self.foreign_match_type = TableConstraint.match_types[row[9]] + self.foreign_keys = row[10] class TableIndex(DbConn.TableIndex): - def __init__(self, row): - self.name, columns = row - self.columns = list(map(int, columns.split(' '))) + def __init__(self, row): + self.name, columns = row + self.columns = list(map(int, columns.split(' '))) class TableTrigger(DbConn.TableTrigger): - def __init__(self, row): - self.name, self.function, self.type, self.enabled = row + def __init__(self, row): + self.name, self.function, self.type, self.enabled = row class TableRule(DbConn.TableRule): - def __init__(self, row): - self.name, self.definition = row + def __init__(self, row): + self.name, self.definition = row class DbError(DbConn.DbError): - def __init__(self, error, query=None): - # save error. funny that the variables are in utf8, not - msg = str( error.args[0], 'utf-8') - if query == None: - if hasattr(error, "cursor") and hasattr(error.cursor, "query"): - query = str(error.cursor.query, 'utf-8') - else: - query = str(query) - DbConn.DbError.__init__(self, msg, query) - + def __init__(self, error, query=None): + msg = str(error.args[0]) + if query == None: + if hasattr(error, "cursor") and hasattr(error.cursor, "query"): + query = str(error.cursor.query) + else: + query = str(query) + DbConn.DbError.__init__(self, msg, query) + class TableField(DbConn.TableField): - def __init__(self, name, data_type, is_null=None, default=None, modifier=None): - self.name, self.data_type, self.is_null, self.default, self.modifier = name, data_type, is_null, default, modifier - + def __init__(self, name, data_type, is_null=None, default=None, modifier=None): + self.name, self.data_type, self.is_null, self.default, self.modifier = name, data_type, is_null, default, modifier + class Connection(DbConn.Connection): - @classmethod - def getTypeName(self): - return 'postgis' - - @classmethod - def getTypeNameString(self): - return 'PostgreSQL' - - @classmethod - def getProviderName(self): - return 'postgres' - - @classmethod - def getSettingsKey(self): - return 'PostgreSQL' - - @classmethod - def icon(self): - return QIcon(":/icons/postgis_elephant.png") - - @classmethod - def connect(self, selected, parent=None): - settings = QSettings() - settings.beginGroup( u"/%s/connections/%s" % (self.getSettingsKey(), selected) ) - - if not settings.contains( "database" ): # non-existent entry? - raise DbError( 'there is no defined database connection "%s".' % selected ) - - get_value_str = lambda x: str(settings.value(x) if Utils.isSIPv2() else settings.value(x).toString()) - service, host, port, database, username, password = list(map(get_value_str, ["service", "host", "port", "database", "username", "password"])) - - # qgis1.5 use 'savePassword' instead of 'save' setting - isSave = settings.value("save") if Utils.isSIPv2() else settings.value("save").toBool() - isSavePassword = settings.value("savePassword") if Utils.isSIPv2() else settings.value("savePassword").toBool() - if not ( isSave or isSavePassword ): - (password, ok) = QInputDialog.getText(parent, "Enter password", 'Enter password for connection "%s":' % selected, QLineEdit.Password) - if not ok: return - - settings.endGroup() - - uri = qgis.core.QgsDataSourceUri() - if service: - uri.setConnection(service, database, username, password) - else: - uri.setConnection(host, port, database, username, password) - - return Connection(uri) - - - def __init__(self, uri): - DbConn.Connection.__init__(self, uri) - - self.service = uri.service() - self.host = uri.host() - self.port = uri.port() - self.dbname = uri.database() - self.user = uri.username() - self.passwd = uri.password() - - try: - self.con = psycopg2.connect(self.con_info()) - except psycopg2.OperationalError as e: - raise DbError(e) - - if not self.dbname: - self.dbname = self.get_dbname() - - self.has_spatial = self.check_spatial() - - self.check_geometry_columns_table() - - # a counter to ensure that the cursor will be unique - self.last_cursor_id = 0 - - def con_info(self): - con_str = '' - if self.service: con_str += "service='%s' " % self.service - if self.host: con_str += "host='%s' " % self.host - if self.port: con_str += "port=%s " % self.port - if self.dbname: con_str += "dbname='%s' " % self.dbname - if self.user: con_str += "user='%s' " % self.user - if self.passwd: con_str += "password='%s' " % self.passwd - return con_str - - def get_dbname(self): - c = self.con.cursor() - self._exec_sql(c, "SELECT current_database()") - return c.fetchone()[0] - - def get_info(self): - c = self.con.cursor() - self._exec_sql(c, "SELECT version()") - return c.fetchone()[0] - - def check_spatial(self): - """ check whether postgis_version is present in catalog """ - c = self.con.cursor() - self._exec_sql(c, "SELECT COUNT(*) FROM pg_proc WHERE proname = 'postgis_version'") - return (c.fetchone()[0] > 0) - - def get_spatial_info(self): - """ returns tuple about postgis support: - - lib version - - installed scripts version - - released scripts version - - geos version - - proj version - - whether uses stats - """ - c = self.con.cursor() - self._exec_sql(c, "SELECT postgis_lib_version(), postgis_scripts_installed(), postgis_scripts_released(), postgis_geos_version(), postgis_proj_version(), postgis_uses_stats()") - return c.fetchone() - - def check_geometry_columns_table(self): - - c = self.con.cursor() - self._exec_sql(c, "SELECT relname FROM pg_class WHERE relname = 'geometry_columns' AND pg_class.relkind IN ('v', 'r')") - self.has_geometry_columns = (len(c.fetchall()) != 0) - - if not self.has_geometry_columns: - self.has_geometry_columns_access = False - return - - # find out whether has privileges to access geometry_columns table - self.has_geometry_columns_access = self.get_table_privileges('geometry_columns')[0] - - - def list_schemas(self): - """ - get list of schemas in tuples: (oid, name, owner, perms) - """ - c = self.con.cursor() - sql = "SELECT oid, nspname, pg_get_userbyid(nspowner), nspacl FROM pg_namespace WHERE nspname !~ '^pg_' AND nspname != 'information_schema'" - self._exec_sql(c, sql) - - schema_cmp = lambda x,y: -1 if x[1] < y[1] else 1 - - return sorted(c.fetchall(), cmp=schema_cmp) - - def list_geotables(self, schema=None): - """ - get list of tables with schemas, whether user has privileges, whether table has geometry column(s) etc. - - geometry_columns: - - f_table_schema - - f_table_name - - f_geometry_column - - coord_dimension - - srid - - type - """ - c = self.con.cursor() - - if schema: - schema_where = " AND nspname = '%s' " % self._quote_str(schema) - else: - schema_where = " AND (nspname != 'information_schema' AND nspname !~ 'pg_') " - - # LEFT OUTER JOIN: like LEFT JOIN but if there are more matches, for join, all are used (not only one) - - # first find out whether postgis is enabled - if not self.has_spatial: - # get all tables and views - sql = """SELECT pg_class.relname, pg_namespace.nspname, pg_class.relkind, pg_get_userbyid(relowner), reltuples, relpages, NULL, NULL, NULL, NULL - FROM pg_class - JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace - WHERE pg_class.relkind IN ('v', 'r')""" + schema_where + "ORDER BY nspname, relname" - else: - # discovery of all tables and whether they contain a geometry column - sql = """SELECT pg_class.relname, pg_namespace.nspname, pg_class.relkind, pg_get_userbyid(relowner), reltuples, relpages, pg_attribute.attname, pg_attribute.atttypid::regtype, NULL, NULL - FROM pg_class - JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace - LEFT OUTER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid AND - ( pg_attribute.atttypid = 'geometry'::regtype - OR pg_attribute.atttypid IN (SELECT oid FROM pg_type WHERE typbasetype='geometry'::regtype ) ) - WHERE pg_class.relkind IN ('v', 'r')""" + schema_where + "ORDER BY nspname, relname, attname" - - self._exec_sql(c, sql) - items = c.fetchall() - - # get geometry info from geometry_columns if exists - if self.has_spatial and self.has_geometry_columns and self.has_geometry_columns_access: - sql = """SELECT relname, nspname, relkind, pg_get_userbyid(relowner), reltuples, relpages, - geometry_columns.f_geometry_column, geometry_columns.type, geometry_columns.coord_dimension, geometry_columns.srid - FROM pg_class - JOIN pg_namespace ON relnamespace=pg_namespace.oid - LEFT OUTER JOIN geometry_columns ON relname=f_table_name AND nspname=f_table_schema - WHERE (relkind = 'r' or relkind='v') """ + schema_where + "ORDER BY nspname, relname, f_geometry_column" - self._exec_sql(c, sql) - - # merge geometry info to "items" - for i, geo_item in enumerate(c.fetchall()): - if geo_item[7]: - items[i] = geo_item - - return items - - - def get_table_rows(self, table, schema=None): - c = self.con.cursor() - self._exec_sql(c, "SELECT COUNT(*) FROM %s" % self._table_name(schema, table)) - return c.fetchone()[0] - - - def get_table_fields(self, table, schema=None): - """ return list of columns in table """ - c = self.con.cursor() - schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" - sql = """SELECT a.attnum AS ordinal_position, - a.attname AS column_name, - t.typname AS data_type, - a.attlen AS char_max_len, - a.atttypmod AS modifier, - a.attnotnull AS notnull, - a.atthasdef AS hasdefault, - adef.adsrc AS default_value - FROM pg_class c - JOIN pg_attribute a ON a.attrelid = c.oid - JOIN pg_type t ON a.atttypid = t.oid - JOIN pg_namespace nsp ON c.relnamespace = nsp.oid - LEFT JOIN pg_attrdef adef ON adef.adrelid = a.attrelid AND adef.adnum = a.attnum - WHERE - c.relname = '%s' %s AND - a.attnum > 0 - ORDER BY a.attnum""" % (self._quote_str(table), schema_where) - - self._exec_sql(c, sql) - attrs = [] - for row in c.fetchall(): - attrs.append(TableAttribute(row)) - return attrs - - - def get_table_indexes(self, table, schema=None): - """ get info about table's indexes. ignore primary key and unique constraint index, they get listed in constaints """ - c = self.con.cursor() - - schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" - sql = """SELECT relname, indkey FROM pg_class, pg_index - WHERE pg_class.oid = pg_index.indexrelid AND pg_class.oid IN ( - SELECT indexrelid FROM pg_index, pg_class - JOIN pg_namespace nsp ON pg_class.relnamespace = nsp.oid - WHERE pg_class.relname='%s' %s AND pg_class.oid=pg_index.indrelid - AND indisprimary != 't' )""" % (self._quote_str(table), schema_where) # AND indisunique != 't' - self._exec_sql(c, sql) - indexes = [] - for row in c.fetchall(): - indexes.append(TableIndex(row)) - return indexes - - - def get_table_unique_indexes(self, table, schema=None): - """ get all the unique indexes """ - schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" - sql = """SELECT relname, indkey - FROM pg_index JOIN pg_class ON pg_index.indrelid=pg_class.oid - JOIN pg_namespace nsp ON pg_class.relnamespace = nsp.oid - WHERE pg_class.relname='%s' %s - AND indisprimary != 't' AND indisunique = 't'""" % (self._quote_str(table), schema_where) - c = self.con.cursor() - self._exec_sql(c, sql) - uniqueIndexes = [] - for row in c.fetchall(): - uniqueIndexes.append(TableIndex(row)) - return uniqueIndexes - - - def get_table_constraints(self, table, schema=None): - c = self.con.cursor() - - schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" - sql = """SELECT c.conname, c.contype, c.condeferrable, c.condeferred, array_to_string(c.conkey, ' '), c.consrc, - t2.relname, c.confupdtype, c.confdeltype, c.confmatchtype, array_to_string(c.confkey, ' ') FROM pg_constraint c - LEFT JOIN pg_class t ON c.conrelid = t.oid - LEFT JOIN pg_class t2 ON c.confrelid = t2.oid - JOIN pg_namespace nsp ON t.relnamespace = nsp.oid - WHERE t.relname = '%s' %s """ % (self._quote_str(table), schema_where) - - self._exec_sql(c, sql) - - constrs = [] - for row in c.fetchall(): - constrs.append(TableConstraint(row)) - return constrs - - - def get_table_triggers(self, table, schema=None): - c = self.con.cursor() - - schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" - sql = """ SELECT tgname, proname, tgtype, tgenabled FROM pg_trigger trig - LEFT JOIN pg_class t ON trig.tgrelid = t.oid - LEFT JOIN pg_proc p ON trig.tgfoid = p.oid - JOIN pg_namespace nsp ON t.relnamespace = nsp.oid - WHERE t.relname ='%s' %s """ % (self._quote_str(table), schema_where) - - self._exec_sql(c, sql) - - triggers = [] - for row in c.fetchall(): - triggers.append(TableTrigger(row)) - return triggers - - - def get_table_rules(self, table, schema=None): - c = self.con.cursor() - - schema_where = " AND schemaname='%s' " % self._quote_str(schema) if schema is not None else "" - sql = """ SELECT rulename, definition FROM pg_rules - WHERE tablename='%s' %s """ % (self._quote_str(table), schema_where) - - self._exec_sql(c, sql) - - rules = [] - for row in c.fetchall(): - rules.append(TableRule(row)) - - return rules - - def get_table_estimated_extent(self, geom, table, schema=None): - """ find out estimated extent (from the statistics) """ - c = self.con.cursor() - - extent = "estimated_extent('%s','%s','%s')" % (self._quote_str(schema), self._quote_str(table), self._quote_str(geom)) - sql = """ SELECT xmin(%(ext)s), ymin(%(ext)s), xmax(%(ext)s), ymax(%(ext)s) """ % { 'ext' : extent } - self._exec_sql(c, sql) - - row = c.fetchone() - return row - - def get_view_definition(self, view, schema=None): - """ returns definition of the view """ - schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" - sql = """SELECT pg_get_viewdef(c.oid) FROM pg_class c - JOIN pg_namespace nsp ON c.relnamespace = nsp.oid - WHERE relname='%s' %s AND relkind='v'""" % (self._quote_str(view), schema_where) - c = self.con.cursor() - self._exec_sql(c, sql) - return c.fetchone()[0] - - """ - def list_tables(self): - c = self.con.cursor() - c.execute("SELECT relname FROM pg_class WHERE relname !~ '^(pg_|sql_)' AND relkind = 'r'") - return c.fetchall() - """ - - def add_geometry_column(self, table, geom_type, schema=None, geom_column='the_geom', srid=-1, dim=2): - - # use schema if explicitly specified - if schema: - schema_part = "'%s', " % self._quote_str(schema) - else: - schema_part = "" - sql = "SELECT AddGeometryColumn(%s'%s', '%s', %d, '%s', %d)" % (schema_part, self._quote_str(table), self._quote_str(geom_column), srid, self._quote_str(geom_type), dim) - self._exec_sql_and_commit(sql) - - def delete_geometry_column(self, table, geom_column, schema=None): - """ use postgis function to delete geometry column correctly """ - if schema: - schema_part = "'%s', " % self._quote_str(schema) - else: - schema_part = "" - sql = "SELECT DropGeometryColumn(%s'%s', '%s')" % (schema_part, self._quote_str(table), self._quote_str(geom_column)) - self._exec_sql_and_commit(sql) - - def delete_geometry_table(self, table, schema=None): - """ delete table with one or more geometries using postgis function """ - if schema: - schema_part = "'%s', " % self._quote_str(schema) - else: - schema_part = "" - sql = "SELECT DropGeometryTable(%s'%s')" % (schema_part, self._quote_str(table)) - self._exec_sql_and_commit(sql) - - def create_table(self, table, fields, pkey=None, schema=None): - """ create ordinary table - 'fields' is array containing instances of TableField - 'pkey' contains name of column to be used as primary key - """ - - if len(fields) == 0: - return False - - table_name = self._table_name(schema, table) - - sql = "CREATE TABLE %s (%s" % (table_name, fields[0].field_def(self)) - for field in fields[1:]: - sql += ", %s" % field.field_def(self) - if pkey: - sql += ", PRIMARY KEY (%s)" % self._quote(pkey) - sql += ")" - self._exec_sql_and_commit(sql) - return True - - def delete_table(self, table, schema=None): - """ delete table from the database """ - table_name = self._table_name(schema, table) - sql = "DROP TABLE %s" % table_name - self._exec_sql_and_commit(sql) - - def empty_table(self, table, schema=None): - """ delete all rows from table """ - table_name = self._table_name(schema, table) - sql = "TRUNCATE %s" % table_name - self._exec_sql_and_commit(sql) - - def rename_table(self, table, new_table, schema=None): - """ rename a table in database """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s RENAME TO %s" % (table_name, self._quote(new_table)) - self._exec_sql_and_commit(sql) - - # update geometry_columns if postgis is enabled - if self.has_spatial and self.has_geometry_columns and self.has_geometry_columns_access: - sql = "UPDATE geometry_columns SET f_table_name='%s' WHERE f_table_name='%s'" % (self._quote_str(new_table), self._quote_str(table)) - if schema is not None: - sql += " AND f_table_schema='%s'" % self._quote_str(schema) - self._exec_sql_and_commit(sql) - - def create_view(self, name, query, schema=None): - view_name = self._table_name(schema, name) - sql = "CREATE VIEW %s AS %s" % (view_name, query) - self._exec_sql_and_commit(sql) - - def delete_view(self, name, schema=None): - view_name = self._table_name(schema, name) - sql = "DROP VIEW %s" % view_name - self._exec_sql_and_commit(sql) - - def rename_view(self, name, new_name, schema=None): - """ rename view in database """ - self.rename_table(name, new_name, schema) - - def create_schema(self, schema): - """ create a new empty schema in database """ - sql = "CREATE SCHEMA %s" % self._quote(schema) - self._exec_sql_and_commit(sql) - - def delete_schema(self, schema): - """ drop (empty) schema from database """ - sql = "DROP SCHEMA %s" % self._quote(schema) - self._exec_sql_and_commit(sql) - - def rename_schema(self, schema, new_schema): - """ rename a schema in database """ - sql = "ALTER SCHEMA %s RENAME TO %s" % (self._quote(schema), self._quote(new_schema)) - self._exec_sql_and_commit(sql) - - # update geometry_columns if postgis is enabled - if self.has_spatial: - sql = "UPDATE geometry_columns SET f_table_schema='%s' WHERE f_table_schema='%s'" % (self._quote_str(new_schema), self._quote_str(schema)) - self._exec_sql_and_commit(sql) - - def table_add_column(self, table, field, schema=None): - """ add a column to table (passed as TableField instance) """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s ADD %s" % (table_name, field.field_def(self)) - self._exec_sql_and_commit(sql) - - def table_delete_column(self, table, field, schema=None): - """ delete column from a table """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s DROP %s" % (table_name, self._quote(field)) - self._exec_sql_and_commit(sql) - - def table_column_rename(self, table, name, new_name, schema=None): - """ rename column in a table """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s RENAME %s TO %s" % (table_name, self._quote(name), self._quote(new_name)) - self._exec_sql_and_commit(sql) - - # update geometry_columns if postgis is enabled - if self.has_spatial: - sql = "UPDATE geometry_columns SET f_geometry_column='%s' WHERE f_geometry_column='%s' AND f_table_name='%s'" % (self._quote_str(new_name), self._quote_str(name), self._quote_str(table)) - if schema is not None: - sql += " AND f_table_schema='%s'" % self._quote(schema) - self._exec_sql_and_commit(sql) - - def table_column_set_type(self, table, column, data_type, schema=None): - """ change column type """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s ALTER %s TYPE %s" % (table_name, self._quote(column), data_type) - self._exec_sql_and_commit(sql) - - def table_column_set_default(self, table, column, default, schema=None): - """ change column's default value. If default=None drop default value """ - table_name = self._table_name(schema, table) - if default: - sql = "ALTER TABLE %s ALTER %s SET DEFAULT %s" % (table_name, self._quote(column), default) - else: - sql = "ALTER TABLE %s ALTER %s DROP DEFAULT" % (table_name, self._quote(column)) - self._exec_sql_and_commit(sql) - - def table_column_set_null(self, table, column, is_null, schema=None): - """ change whether column can contain null values """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s ALTER %s " % (table_name, self._quote(column)) - if is_null: - sql += "DROP NOT NULL" - else: - sql += "SET NOT NULL" - self._exec_sql_and_commit(sql) - - def table_add_primary_key(self, table, column, schema=None): - """ add a primery key (with one column) to a table """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s ADD PRIMARY KEY (%s)" % (table_name, self._quote(column)) - self._exec_sql_and_commit(sql) - - def table_add_unique_constraint(self, table, column, schema=None): - """ add a unique constraint to a table """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s ADD UNIQUE (%s)" % (table_name, self._quote(column)) - self._exec_sql_and_commit(sql) - - def table_delete_constraint(self, table, constraint, schema=None): - """ delete constraint in a table """ - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s DROP CONSTRAINT %s" % (table_name, self._quote(constraint)) - self._exec_sql_and_commit(sql) - - def table_move_to_schema(self, table, new_schema, schema=None): - if new_schema == schema: - return - table_name = self._table_name(schema, table) - sql = "ALTER TABLE %s SET SCHEMA %s" % (table_name, self._quote(new_schema)) - self._exec_sql_and_commit(sql) - - # update geometry_columns if postgis is enabled - if self.has_spatial: - sql = "UPDATE geometry_columns SET f_table_schema='%s' WHERE f_table_name='%s'" % (self._quote_str(new_schema), self._quote_str(table)) - if schema is not None: - sql += " AND f_table_schema='%s'" % self._quote_str(schema) - self._exec_sql_and_commit(sql) - - def table_apply_function(self, schema, table, res_column, fct, param): - """ apply a function to a column and save the result in other column """ - table = self._table_name(schema, table) - sql = "UPDATE %s SET %s = %s(%s)" % (table, self._quote(res_column), fct, self._quote(param)) - self._exec_sql_and_commit(sql) - - def table_enable_triggers(self, table, schema, enable=True): - """ enable or disable all triggers on table """ - table = self._table_name(schema, table) - sql = "ALTER TABLE %s %s TRIGGER ALL" % (table, "ENABLE" if enable else "DISABLE") - self._exec_sql_and_commit(sql) - - def table_enable_trigger(self, table, schema, trigger, enable=True): - """ enable or disable one trigger on table """ - table = self._table_name(schema, table) - sql = "ALTER TABLE %s %s TRIGGER %s" % (table, "ENABLE" if enable else "DISABLE", self._quote(trigger)) - self._exec_sql_and_commit(sql) - - def table_delete_trigger(self, table, schema, trigger): - """ delete trigger on table """ - table = self._table_name(schema, table) - sql = "DROP TRIGGER %s ON %s" % (self._quote(trigger), table) - self._exec_sql_and_commit(sql) - - def table_delete_rule(self, table, schema, rule): - """ delete rule on table """ - table = self._table_name(schema, table) - sql = "DROP RULE %s ON %s" % (self._quote(rule), table) - self._exec_sql_and_commit(sql) - - def create_index(self, table, name, column, schema=None): - """ create index on one column using default options """ - table_name = self._table_name(schema, table) - idx_name = self._quote(name) - sql = "CREATE INDEX %s ON %s (%s)" % (idx_name, table_name, self._quote(column)) - self._exec_sql_and_commit(sql) - - def create_spatial_index(self, table, schema=None, geom_column='the_geom'): - table_name = self._table_name(schema, table) - idx_name = self._quote("sidx_"+table) - sql = "CREATE INDEX %s ON %s USING GIST(%s GIST_GEOMETRY_OPS)" % (idx_name, table_name, self._quote(geom_column)) - self._exec_sql_and_commit(sql) - - def delete_index(self, name, schema=None): - index_name = self._table_name(schema, name) - sql = "DROP INDEX %s" % index_name - self._exec_sql_and_commit(sql) - - def get_database_privileges(self): - """ db privileges: (can create schemas, can create temp. tables) """ - sql = "SELECT has_database_privilege('%(d)s', 'CREATE'), has_database_privilege('%(d)s', 'TEMP')" % { 'd' : self._quote_str(self.dbname) } - c = self.con.cursor() - self._exec_sql(c, sql) - return c.fetchone() - - def get_schema_privileges(self, schema): - """ schema privileges: (can create new objects, can access objects in schema) """ - sql = "SELECT has_schema_privilege('%(s)s', 'CREATE'), has_schema_privilege('%(s)s', 'USAGE')" % { 's' : self._quote_str(schema) } - c = self.con.cursor() - self._exec_sql(c, sql) - return c.fetchone() - - def get_table_privileges(self, table, schema=None): - """ table privileges: (select, insert, update, delete) """ - t = self._table_name(schema, table) - sql = """SELECT has_table_privilege('%(t)s', 'SELECT'), has_table_privilege('%(t)s', 'INSERT'), - has_table_privilege('%(t)s', 'UPDATE'), has_table_privilege('%(t)s', 'DELETE')""" % { 't': self._quote_str(t) } - c = self.con.cursor() - self._exec_sql(c, sql) - return c.fetchone() - - def vacuum_analyze(self, table, schema=None): - """ run vacuum analyze on a table """ - t = self._table_name(schema, table) - # vacuum analyze must be run outside transaction block - we have to change isolation level - self.con.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) - c = self.con.cursor() - self._exec_sql(c, "VACUUM ANALYZE %s" % t) - self.con.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED) - - def sr_info_for_srid(self, srid): - if not self.has_spatial: - return "Unknown" - - try: - c = self.con.cursor() - self._exec_sql(c, "SELECT srtext FROM spatial_ref_sys WHERE srid = '%d'" % srid) - sr = c.fetchone() - if sr is None: - return "Unknown" - srtext = sr[0] - # try to extract just SR name (should be qouted in double quotes) - x = re.search('"([^"]+)"', srtext) - if x is not None: - srtext = x.group() - return srtext - except DbError as e: - return "Unknown" - - def insert_table_row(self, table, values, schema=None, cursor=None): - """ insert a row with specified values to a table. - if a cursor is specified, it doesn't commit (expecting that there will be more inserts) - otherwise it commits immediately """ - t = self._table_name(schema, table) - sql = "" - for value in values: - # TODO: quote values? - if sql: sql += ", " - sql += value - sql = "INSERT INTO %s VALUES (%s)" % (t, sql) - if cursor: - self._exec_sql(cursor, sql) - else: - self._exec_sql_and_commit(sql) - - - def table_add_function_trigger(self, schema, table, resColumn, fct, geomColumn): - """ add a trigger on insert and update that recalculates the value from geometry column """ - - trig_f_name = "%s_calc_%s" % (table, fct) - trig_name = "calc_%s" % fct - ctx = { 'fname' : trig_f_name, 'tname' : trig_name, - 'res' : resColumn, 'geom' : geomColumn, - 'f' : fct, 'table' : self._table_name(schema, table) } - sql = """ - CREATE OR REPLACE FUNCTION %(fname)s() RETURNS TRIGGER AS - $$ - BEGIN - IF (TG_OP = 'INSERT') THEN - NEW.%(res)s := %(f)s(NEW.%(geom)s); - ELSIF (TG_OP = 'UPDATE') THEN - IF NOT (NEW.%(geom)s ~= OLD.%(geom)s) THEN - NEW.%(res)s := %(f)s(NEW.%(geom)s); - END IF; - END IF; - RETURN NEW; - END; - $$ - LANGUAGE 'plpgsql'; - - CREATE TRIGGER %(tname)s BEFORE INSERT OR UPDATE ON %(table)s FOR EACH ROW - EXECUTE PROCEDURE %(fname)s(); - """ % ctx - - self._exec_sql_and_commit(sql) - - - def get_named_cursor(self, table=None): - """ return an unique named cursor, optionally including a table name """ - self.last_cursor_id += 1 - if table is not None: - table2 = re.sub(r'\W', '_', table.encode('ascii','replace')) # all non-alphanum characters to underscore - cur_name = "cursor_%d_table_%s" % (self.last_cursor_id, table2) - else: - cur_name = "cursor_%d" % self.last_cursor_id - #cur_name = ("\"db_table_"+self.table+"\"").replace(' ', '_') - #cur_name = cur_name.encode('ascii','replace').replace('?', '_') - return self.con.cursor(cur_name) - - def _exec_sql(self, cursor, sql): - try: - cursor.execute(sql) - except psycopg2.Error as e: - # do the rollback to avoid a "current transaction aborted, commands ignored" errors - self.con.rollback() - raise DbError(e) - - def _exec_sql_and_commit(self, sql): - """ tries to execute and commit some action, on error it rolls back the change """ - #try: - c = self.con.cursor() - self._exec_sql(c, sql) - self.con.commit() - #except DbError, e: - # self.con.rollback() - # raise - - def _quote(self, identifier): - identifier = str(identifier) # make sure it's python unicode string - return u'"%s"' % identifier.replace('"', '""') - - def _quote_str(self, txt): - """ make the string safe - replace ' with '' """ - txt = str(txt) # make sure it's python unicode string - return txt.replace("'", "''") - - def _table_name(self, schema, table): - if not schema: - return self._quote(table) - else: - return u"%s.%s" % (self._quote(schema), self._quote(table)) - + @classmethod + def getTypeName(self): + return 'postgis' + + @classmethod + def getTypeNameString(self): + return 'PostgreSQL' + + @classmethod + def getProviderName(self): + return 'postgres' + + @classmethod + def getSettingsKey(self): + return 'PostgreSQL' + + @classmethod + def icon(self): + return QIcon(":/icons/postgis_elephant.png") + + @classmethod + def connect(self, selected, parent=None): + settings = QSettings() + settings.beginGroup( u"/%s/connections/%s" % (self.getSettingsKey(), selected) ) + + if not settings.contains( "database" ): # non-existent entry? + raise DbError( 'there is no defined database connection "%s".' % selected ) + + get_value_str = lambda x: str(settings.value(x) if Utils.isSIPv2() else settings.value(x).toString()) + service, host, port, database, username, password = list(map(get_value_str, ["service", "host", "port", "database", "username", "password"])) + + # qgis1.5 use 'savePassword' instead of 'save' setting + isSave = settings.value("save") if Utils.isSIPv2() else settings.value("save").toBool() + isSavePassword = settings.value("savePassword") if Utils.isSIPv2() else settings.value("savePassword").toBool() + if not ( isSave or isSavePassword ): + (password, ok) = QInputDialog.getText(parent, "Enter password", 'Enter password for connection "%s":' % selected, QLineEdit.Password) + if not ok: return + + settings.endGroup() + + uri = QgsDataSourceUri() + if service: + uri.setConnection(service, database, username, password) + else: + uri.setConnection(host, port, database, username, password) + + return Connection(uri) + + + def __init__(self, uri): + DbConn.Connection.__init__(self, uri) + + self.service = uri.service() + self.host = uri.host() + self.port = uri.port() + self.dbname = uri.database() + self.user = uri.username() + self.passwd = uri.password() + + try: + self.con = psycopg2.connect(self.con_info()) + except psycopg2.OperationalError as e: + raise DbError(e) + + if not self.dbname: + self.dbname = self.get_dbname() + + self.has_spatial = self.check_spatial() + + self.check_geometry_columns_table() + + # a counter to ensure that the cursor will be unique + self.last_cursor_id = 0 + + def con_info(self): + con_str = '' + if self.service: con_str += "service='%s' " % self.service + if self.host: con_str += "host='%s' " % self.host + if self.port: con_str += "port=%s " % self.port + if self.dbname: con_str += "dbname='%s' " % self.dbname + if self.user: con_str += "user='%s' " % self.user + if self.passwd: con_str += "password='%s' " % self.passwd + return con_str + + def get_dbname(self): + c = self.con.cursor() + self._exec_sql(c, "SELECT current_database()") + return c.fetchone()[0] + + def get_info(self): + c = self.con.cursor() + self._exec_sql(c, "SELECT version()") + return c.fetchone()[0] + + def check_spatial(self): + """ check whether postgis_version is present in catalog """ + c = self.con.cursor() + self._exec_sql(c, "SELECT COUNT(*) FROM pg_proc WHERE proname = 'postgis_version'") + return (c.fetchone()[0] > 0) + + def get_spatial_info(self): + """ returns tuple about postgis support: + - lib version + - installed scripts version + - released scripts version + - geos version + - proj version + - whether uses stats + """ + c = self.con.cursor() + self._exec_sql(c, "SELECT postgis_lib_version(), postgis_scripts_installed(), postgis_scripts_released(), postgis_geos_version(), postgis_proj_version(), postgis_uses_stats()") + return c.fetchone() + + def check_geometry_columns_table(self): + + c = self.con.cursor() + self._exec_sql(c, "SELECT relname FROM pg_class WHERE relname = 'geometry_columns' AND pg_class.relkind IN ('v', 'r')") + self.has_geometry_columns = (len(c.fetchall()) != 0) + + if not self.has_geometry_columns: + self.has_geometry_columns_access = False + return + + # find out whether has privileges to access geometry_columns table + self.has_geometry_columns_access = self.get_table_privileges('geometry_columns')[0] + + + def list_schemas(self): + """ + get list of schemas in tuples: (oid, name, owner, perms) + """ + c = self.con.cursor() + sql = "SELECT oid, nspname, pg_get_userbyid(nspowner), nspacl FROM pg_namespace WHERE nspname !~ '^pg_' AND nspname != 'information_schema'" + self._exec_sql(c, sql) + + schema_cmp = lambda x,y: -1 if x[1] < y[1] else 1 + + return sorted(c.fetchall(), cmp=schema_cmp) + + def list_geotables(self, schema=None): + """ + get list of tables with schemas, whether user has privileges, whether table has geometry column(s) etc. + + geometry_columns: + - f_table_schema + - f_table_name + - f_geometry_column + - coord_dimension + - srid + - type + """ + c = self.con.cursor() + + if schema: + schema_where = " AND nspname = '%s' " % self._quote_str(schema) + else: + schema_where = " AND (nspname != 'information_schema' AND nspname !~ 'pg_') " + + # LEFT OUTER JOIN: like LEFT JOIN but if there are more matches, for join, all are used (not only one) + + # first find out whether postgis is enabled + if not self.has_spatial: + # get all tables and views + sql = """SELECT pg_class.relname, pg_namespace.nspname, pg_class.relkind, pg_get_userbyid(relowner), reltuples, relpages, NULL, NULL, NULL, NULL + FROM pg_class + JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace + WHERE pg_class.relkind IN ('v', 'r')""" + schema_where + "ORDER BY nspname, relname" + else: + # discovery of all tables and whether they contain a geometry column + sql = """SELECT pg_class.relname, pg_namespace.nspname, pg_class.relkind, pg_get_userbyid(relowner), reltuples, relpages, pg_attribute.attname, pg_attribute.atttypid::regtype, NULL, NULL + FROM pg_class + JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace + LEFT OUTER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid AND + ( pg_attribute.atttypid = 'geometry'::regtype + OR pg_attribute.atttypid IN (SELECT oid FROM pg_type WHERE typbasetype='geometry'::regtype ) ) + WHERE pg_class.relkind IN ('v', 'r')""" + schema_where + "ORDER BY nspname, relname, attname" + + self._exec_sql(c, sql) + items = c.fetchall() + + # get geometry info from geometry_columns if exists + if self.has_spatial and self.has_geometry_columns and self.has_geometry_columns_access: + sql = """SELECT relname, nspname, relkind, pg_get_userbyid(relowner), reltuples, relpages, + geometry_columns.f_geometry_column, geometry_columns.type, geometry_columns.coord_dimension, geometry_columns.srid + FROM pg_class + JOIN pg_namespace ON relnamespace=pg_namespace.oid + LEFT OUTER JOIN geometry_columns ON relname=f_table_name AND nspname=f_table_schema + WHERE (relkind = 'r' or relkind='v') """ + schema_where + "ORDER BY nspname, relname, f_geometry_column" + self._exec_sql(c, sql) + + # merge geometry info to "items" + for i, geo_item in enumerate(c.fetchall()): + if geo_item[7]: + items[i] = geo_item + + return items + + + def get_table_rows(self, table, schema=None): + c = self.con.cursor() + self._exec_sql(c, "SELECT COUNT(*) FROM %s" % self._table_name(schema, table)) + return c.fetchone()[0] + + + def get_table_fields(self, table, schema=None): + """ return list of columns in table """ + c = self.con.cursor() + schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" + sql = """SELECT a.attnum AS ordinal_position, + a.attname AS column_name, + t.typname AS data_type, + a.attlen AS char_max_len, + a.atttypmod AS modifier, + a.attnotnull AS notnull, + a.atthasdef AS hasdefault, + adef.adsrc AS default_value + FROM pg_class c + JOIN pg_attribute a ON a.attrelid = c.oid + JOIN pg_type t ON a.atttypid = t.oid + JOIN pg_namespace nsp ON c.relnamespace = nsp.oid + LEFT JOIN pg_attrdef adef ON adef.adrelid = a.attrelid AND adef.adnum = a.attnum + WHERE + c.relname = '%s' %s AND + a.attnum > 0 + ORDER BY a.attnum""" % (self._quote_str(table), schema_where) + + self._exec_sql(c, sql) + attrs = [] + for row in c.fetchall(): + attrs.append(TableAttribute(row)) + return attrs + + + def get_table_indexes(self, table, schema=None): + """ get info about table's indexes. ignore primary key and unique constraint index, they get listed in constaints """ + c = self.con.cursor() + + schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" + sql = """SELECT relname, indkey FROM pg_class, pg_index + WHERE pg_class.oid = pg_index.indexrelid AND pg_class.oid IN ( + SELECT indexrelid FROM pg_index, pg_class + JOIN pg_namespace nsp ON pg_class.relnamespace = nsp.oid + WHERE pg_class.relname='%s' %s AND pg_class.oid=pg_index.indrelid + AND indisprimary != 't' )""" % (self._quote_str(table), schema_where) # AND indisunique != 't' + self._exec_sql(c, sql) + indexes = [] + for row in c.fetchall(): + indexes.append(TableIndex(row)) + return indexes + + + def get_table_unique_indexes(self, table, schema=None): + """ get all the unique indexes """ + schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" + sql = """SELECT relname, indkey + FROM pg_index JOIN pg_class ON pg_index.indrelid=pg_class.oid + JOIN pg_namespace nsp ON pg_class.relnamespace = nsp.oid + WHERE pg_class.relname='%s' %s + AND indisprimary != 't' AND indisunique = 't'""" % (self._quote_str(table), schema_where) + c = self.con.cursor() + self._exec_sql(c, sql) + uniqueIndexes = [] + for row in c.fetchall(): + uniqueIndexes.append(TableIndex(row)) + return uniqueIndexes + + + def get_table_constraints(self, table, schema=None): + c = self.con.cursor() + + schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" + sql = """SELECT c.conname, c.contype, c.condeferrable, c.condeferred, array_to_string(c.conkey, ' '), c.consrc, + t2.relname, c.confupdtype, c.confdeltype, c.confmatchtype, array_to_string(c.confkey, ' ') FROM pg_constraint c + LEFT JOIN pg_class t ON c.conrelid = t.oid + LEFT JOIN pg_class t2 ON c.confrelid = t2.oid + JOIN pg_namespace nsp ON t.relnamespace = nsp.oid + WHERE t.relname = '%s' %s """ % (self._quote_str(table), schema_where) + + self._exec_sql(c, sql) + + constrs = [] + for row in c.fetchall(): + constrs.append(TableConstraint(row)) + return constrs + + + def get_table_triggers(self, table, schema=None): + c = self.con.cursor() + + schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" + sql = """ SELECT tgname, proname, tgtype, tgenabled FROM pg_trigger trig + LEFT JOIN pg_class t ON trig.tgrelid = t.oid + LEFT JOIN pg_proc p ON trig.tgfoid = p.oid + JOIN pg_namespace nsp ON t.relnamespace = nsp.oid + WHERE t.relname ='%s' %s """ % (self._quote_str(table), schema_where) + + self._exec_sql(c, sql) + + triggers = [] + for row in c.fetchall(): + triggers.append(TableTrigger(row)) + return triggers + + + def get_table_rules(self, table, schema=None): + c = self.con.cursor() + + schema_where = " AND schemaname='%s' " % self._quote_str(schema) if schema is not None else "" + sql = """ SELECT rulename, definition FROM pg_rules + WHERE tablename='%s' %s """ % (self._quote_str(table), schema_where) + + self._exec_sql(c, sql) + + rules = [] + for row in c.fetchall(): + rules.append(TableRule(row)) + + return rules + + def get_table_estimated_extent(self, geom, table, schema=None): + """ find out estimated extent (from the statistics) """ + c = self.con.cursor() + + extent = "estimated_extent('%s','%s','%s')" % (self._quote_str(schema), self._quote_str(table), self._quote_str(geom)) + sql = """ SELECT xmin(%(ext)s), ymin(%(ext)s), xmax(%(ext)s), ymax(%(ext)s) """ % { 'ext' : extent } + self._exec_sql(c, sql) + + row = c.fetchone() + return row + + def get_view_definition(self, view, schema=None): + """ returns definition of the view """ + schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else "" + sql = """SELECT pg_get_viewdef(c.oid) FROM pg_class c + JOIN pg_namespace nsp ON c.relnamespace = nsp.oid + WHERE relname='%s' %s AND relkind='v'""" % (self._quote_str(view), schema_where) + c = self.con.cursor() + self._exec_sql(c, sql) + return c.fetchone()[0] + + """ + def list_tables(self): + c = self.con.cursor() + c.execute("SELECT relname FROM pg_class WHERE relname !~ '^(pg_|sql_)' AND relkind = 'r'") + return c.fetchall() + """ + + def add_geometry_column(self, table, geom_type, schema=None, geom_column='the_geom', srid=-1, dim=2): + + # use schema if explicitly specified + if schema: + schema_part = "'%s', " % self._quote_str(schema) + else: + schema_part = "" + sql = "SELECT AddGeometryColumn(%s'%s', '%s', %d, '%s', %d)" % (schema_part, self._quote_str(table), self._quote_str(geom_column), srid, self._quote_str(geom_type), dim) + self._exec_sql_and_commit(sql) + + def delete_geometry_column(self, table, geom_column, schema=None): + """ use postgis function to delete geometry column correctly """ + if schema: + schema_part = "'%s', " % self._quote_str(schema) + else: + schema_part = "" + sql = "SELECT DropGeometryColumn(%s'%s', '%s')" % (schema_part, self._quote_str(table), self._quote_str(geom_column)) + self._exec_sql_and_commit(sql) + + def delete_geometry_table(self, table, schema=None): + """ delete table with one or more geometries using postgis function """ + if schema: + schema_part = "'%s', " % self._quote_str(schema) + else: + schema_part = "" + sql = "SELECT DropGeometryTable(%s'%s')" % (schema_part, self._quote_str(table)) + self._exec_sql_and_commit(sql) + + def create_table(self, table, fields, pkey=None, schema=None): + """ create ordinary table + 'fields' is array containing instances of TableField + 'pkey' contains name of column to be used as primary key + """ + + if len(fields) == 0: + return False + + table_name = self._table_name(schema, table) + + sql = "CREATE TABLE %s (%s" % (table_name, fields[0].field_def(self)) + for field in fields[1:]: + sql += ", %s" % field.field_def(self) + if pkey: + sql += ", PRIMARY KEY (%s)" % self._quote(pkey) + sql += ")" + self._exec_sql_and_commit(sql) + return True + + def delete_table(self, table, schema=None): + """ delete table from the database """ + table_name = self._table_name(schema, table) + sql = "DROP TABLE %s" % table_name + self._exec_sql_and_commit(sql) + + def empty_table(self, table, schema=None): + """ delete all rows from table """ + table_name = self._table_name(schema, table) + sql = "TRUNCATE %s" % table_name + self._exec_sql_and_commit(sql) + + def rename_table(self, table, new_table, schema=None): + """ rename a table in database """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s RENAME TO %s" % (table_name, self._quote(new_table)) + self._exec_sql_and_commit(sql) + + # update geometry_columns if postgis is enabled + if self.has_spatial and self.has_geometry_columns and self.has_geometry_columns_access: + sql = "UPDATE geometry_columns SET f_table_name='%s' WHERE f_table_name='%s'" % (self._quote_str(new_table), self._quote_str(table)) + if schema is not None: + sql += " AND f_table_schema='%s'" % self._quote_str(schema) + self._exec_sql_and_commit(sql) + + def create_view(self, name, query, schema=None): + view_name = self._table_name(schema, name) + sql = "CREATE VIEW %s AS %s" % (view_name, query) + self._exec_sql_and_commit(sql) + + def delete_view(self, name, schema=None): + view_name = self._table_name(schema, name) + sql = "DROP VIEW %s" % view_name + self._exec_sql_and_commit(sql) + + def rename_view(self, name, new_name, schema=None): + """ rename view in database """ + self.rename_table(name, new_name, schema) + + def create_schema(self, schema): + """ create a new empty schema in database """ + sql = "CREATE SCHEMA %s" % self._quote(schema) + self._exec_sql_and_commit(sql) + + def delete_schema(self, schema): + """ drop (empty) schema from database """ + sql = "DROP SCHEMA %s" % self._quote(schema) + self._exec_sql_and_commit(sql) + + def rename_schema(self, schema, new_schema): + """ rename a schema in database """ + sql = "ALTER SCHEMA %s RENAME TO %s" % (self._quote(schema), self._quote(new_schema)) + self._exec_sql_and_commit(sql) + + # update geometry_columns if postgis is enabled + if self.has_spatial: + sql = "UPDATE geometry_columns SET f_table_schema='%s' WHERE f_table_schema='%s'" % (self._quote_str(new_schema), self._quote_str(schema)) + self._exec_sql_and_commit(sql) + + def table_add_column(self, table, field, schema=None): + """ add a column to table (passed as TableField instance) """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s ADD %s" % (table_name, field.field_def(self)) + self._exec_sql_and_commit(sql) + + def table_delete_column(self, table, field, schema=None): + """ delete column from a table """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s DROP %s" % (table_name, self._quote(field)) + self._exec_sql_and_commit(sql) + + def table_column_rename(self, table, name, new_name, schema=None): + """ rename column in a table """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s RENAME %s TO %s" % (table_name, self._quote(name), self._quote(new_name)) + self._exec_sql_and_commit(sql) + + # update geometry_columns if postgis is enabled + if self.has_spatial: + sql = "UPDATE geometry_columns SET f_geometry_column='%s' WHERE f_geometry_column='%s' AND f_table_name='%s'" % (self._quote_str(new_name), self._quote_str(name), self._quote_str(table)) + if schema is not None: + sql += " AND f_table_schema='%s'" % self._quote(schema) + self._exec_sql_and_commit(sql) + + def table_column_set_type(self, table, column, data_type, schema=None): + """ change column type """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s ALTER %s TYPE %s" % (table_name, self._quote(column), data_type) + self._exec_sql_and_commit(sql) + + def table_column_set_default(self, table, column, default, schema=None): + """ change column's default value. If default=None drop default value """ + table_name = self._table_name(schema, table) + if default: + sql = "ALTER TABLE %s ALTER %s SET DEFAULT %s" % (table_name, self._quote(column), default) + else: + sql = "ALTER TABLE %s ALTER %s DROP DEFAULT" % (table_name, self._quote(column)) + self._exec_sql_and_commit(sql) + + def table_column_set_null(self, table, column, is_null, schema=None): + """ change whether column can contain null values """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s ALTER %s " % (table_name, self._quote(column)) + if is_null: + sql += "DROP NOT NULL" + else: + sql += "SET NOT NULL" + self._exec_sql_and_commit(sql) + + def table_add_primary_key(self, table, column, schema=None): + """ add a primery key (with one column) to a table """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s ADD PRIMARY KEY (%s)" % (table_name, self._quote(column)) + self._exec_sql_and_commit(sql) + + def table_add_unique_constraint(self, table, column, schema=None): + """ add a unique constraint to a table """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s ADD UNIQUE (%s)" % (table_name, self._quote(column)) + self._exec_sql_and_commit(sql) + + def table_delete_constraint(self, table, constraint, schema=None): + """ delete constraint in a table """ + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s DROP CONSTRAINT %s" % (table_name, self._quote(constraint)) + self._exec_sql_and_commit(sql) + + def table_move_to_schema(self, table, new_schema, schema=None): + if new_schema == schema: + return + table_name = self._table_name(schema, table) + sql = "ALTER TABLE %s SET SCHEMA %s" % (table_name, self._quote(new_schema)) + self._exec_sql_and_commit(sql) + + # update geometry_columns if postgis is enabled + if self.has_spatial: + sql = "UPDATE geometry_columns SET f_table_schema='%s' WHERE f_table_name='%s'" % (self._quote_str(new_schema), self._quote_str(table)) + if schema is not None: + sql += " AND f_table_schema='%s'" % self._quote_str(schema) + self._exec_sql_and_commit(sql) + + def table_apply_function(self, schema, table, res_column, fct, param): + """ apply a function to a column and save the result in other column """ + table = self._table_name(schema, table) + sql = "UPDATE %s SET %s = %s(%s)" % (table, self._quote(res_column), fct, self._quote(param)) + self._exec_sql_and_commit(sql) + + def table_enable_triggers(self, table, schema, enable=True): + """ enable or disable all triggers on table """ + table = self._table_name(schema, table) + sql = "ALTER TABLE %s %s TRIGGER ALL" % (table, "ENABLE" if enable else "DISABLE") + self._exec_sql_and_commit(sql) + + def table_enable_trigger(self, table, schema, trigger, enable=True): + """ enable or disable one trigger on table """ + table = self._table_name(schema, table) + sql = "ALTER TABLE %s %s TRIGGER %s" % (table, "ENABLE" if enable else "DISABLE", self._quote(trigger)) + self._exec_sql_and_commit(sql) + + def table_delete_trigger(self, table, schema, trigger): + """ delete trigger on table """ + table = self._table_name(schema, table) + sql = "DROP TRIGGER %s ON %s" % (self._quote(trigger), table) + self._exec_sql_and_commit(sql) + + def table_delete_rule(self, table, schema, rule): + """ delete rule on table """ + table = self._table_name(schema, table) + sql = "DROP RULE %s ON %s" % (self._quote(rule), table) + self._exec_sql_and_commit(sql) + + def create_index(self, table, name, column, schema=None): + """ create index on one column using default options """ + table_name = self._table_name(schema, table) + idx_name = self._quote(name) + sql = "CREATE INDEX %s ON %s (%s)" % (idx_name, table_name, self._quote(column)) + self._exec_sql_and_commit(sql) + + def create_spatial_index(self, table, schema=None, geom_column='the_geom'): + table_name = self._table_name(schema, table) + idx_name = self._quote("sidx_"+table) + sql = "CREATE INDEX %s ON %s USING GIST(%s GIST_GEOMETRY_OPS)" % (idx_name, table_name, self._quote(geom_column)) + self._exec_sql_and_commit(sql) + + def delete_index(self, name, schema=None): + index_name = self._table_name(schema, name) + sql = "DROP INDEX %s" % index_name + self._exec_sql_and_commit(sql) + + def get_database_privileges(self): + """ db privileges: (can create schemas, can create temp. tables) """ + sql = "SELECT has_database_privilege('%(d)s', 'CREATE'), has_database_privilege('%(d)s', 'TEMP')" % { 'd' : self._quote_str(self.dbname) } + c = self.con.cursor() + self._exec_sql(c, sql) + return c.fetchone() + + def get_schema_privileges(self, schema): + """ schema privileges: (can create new objects, can access objects in schema) """ + sql = "SELECT has_schema_privilege('%(s)s', 'CREATE'), has_schema_privilege('%(s)s', 'USAGE')" % { 's' : self._quote_str(schema) } + c = self.con.cursor() + self._exec_sql(c, sql) + return c.fetchone() + + def get_table_privileges(self, table, schema=None): + """ table privileges: (select, insert, update, delete) """ + t = self._table_name(schema, table) + sql = """SELECT has_table_privilege('%(t)s', 'SELECT'), has_table_privilege('%(t)s', 'INSERT'), + has_table_privilege('%(t)s', 'UPDATE'), has_table_privilege('%(t)s', 'DELETE')""" % { 't': self._quote_str(t) } + c = self.con.cursor() + self._exec_sql(c, sql) + return c.fetchone() + + def vacuum_analyze(self, table, schema=None): + """ run vacuum analyze on a table """ + t = self._table_name(schema, table) + # vacuum analyze must be run outside transaction block - we have to change isolation level + self.con.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) + c = self.con.cursor() + self._exec_sql(c, "VACUUM ANALYZE %s" % t) + self.con.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED) + + def sr_info_for_srid(self, srid): + if not self.has_spatial: + return "Unknown" + + try: + c = self.con.cursor() + self._exec_sql(c, "SELECT srtext FROM spatial_ref_sys WHERE srid = '%d'" % srid) + sr = c.fetchone() + if sr is None: + return "Unknown" + srtext = sr[0] + # try to extract just SR name (should be qouted in double quotes) + x = re.search('"([^"]+)"', srtext) + if x is not None: + srtext = x.group() + return srtext + except DbError as e: + return "Unknown" + + def insert_table_row(self, table, values, schema=None, cursor=None): + """ insert a row with specified values to a table. + if a cursor is specified, it doesn't commit (expecting that there will be more inserts) + otherwise it commits immediately """ + t = self._table_name(schema, table) + sql = "" + for value in values: + # TODO: quote values? + if sql: sql += ", " + sql += value + sql = "INSERT INTO %s VALUES (%s)" % (t, sql) + if cursor: + self._exec_sql(cursor, sql) + else: + self._exec_sql_and_commit(sql) + + + def table_add_function_trigger(self, schema, table, resColumn, fct, geomColumn): + """ add a trigger on insert and update that recalculates the value from geometry column """ + + trig_f_name = "%s_calc_%s" % (table, fct) + trig_name = "calc_%s" % fct + ctx = { 'fname' : trig_f_name, 'tname' : trig_name, + 'res' : resColumn, 'geom' : geomColumn, + 'f' : fct, 'table' : self._table_name(schema, table) } + sql = """ + CREATE OR REPLACE FUNCTION %(fname)s() RETURNS TRIGGER AS + $$ + BEGIN + IF (TG_OP = 'INSERT') THEN + NEW.%(res)s := %(f)s(NEW.%(geom)s); + ELSIF (TG_OP = 'UPDATE') THEN + IF NOT (NEW.%(geom)s ~= OLD.%(geom)s) THEN + NEW.%(res)s := %(f)s(NEW.%(geom)s); + END IF; + END IF; + RETURN NEW; + END; + $$ + LANGUAGE 'plpgsql'; + + CREATE TRIGGER %(tname)s BEFORE INSERT OR UPDATE ON %(table)s FOR EACH ROW + EXECUTE PROCEDURE %(fname)s(); + """ % ctx + + self._exec_sql_and_commit(sql) + + + def get_named_cursor(self, table=None): + """ return an unique named cursor, optionally including a table name """ + self.last_cursor_id += 1 + if table is not None: + table2 = re.sub(r'\W', '_', table.encode('ascii','replace')) # all non-alphanum characters to underscore + cur_name = "cursor_%d_table_%s" % (self.last_cursor_id, table2) + else: + cur_name = "cursor_%d" % self.last_cursor_id + #cur_name = ("\"db_table_"+self.table+"\"").replace(' ', '_') + #cur_name = cur_name.encode('ascii','replace').replace('?', '_') + return self.con.cursor(cur_name) + + def _exec_sql(self, cursor, sql): + try: + cursor.execute(sql) + except psycopg2.Error as e: + # do the rollback to avoid a "current transaction aborted, commands ignored" errors + self.con.rollback() + raise DbError(e) + + def _exec_sql_and_commit(self, sql): + """ tries to execute and commit some action, on error it rolls back the change """ + #try: + c = self.con.cursor() + self._exec_sql(c, sql) + self.con.commit() + #except DbError, e: + # self.con.rollback() + # raise + + def _quote(self, identifier): + identifier = str(identifier) # make sure it's python unicode string + return u'"%s"' % identifier.replace('"', '""') + + def _quote_str(self, txt): + """ make the string safe - replace ' with '' """ + txt = str(txt) # make sure it's python unicode string + return txt.replace("'", "''") + + def _table_name(self, schema, table): + if not schema: + return self._quote(table) + else: + return u"%s.%s" % (self._quote(schema), self._quote(table)) + # for debugging / testing if __name__ == '__main__': - db = GeoDB(host='localhost',dbname='gis',user='gisak',passwd='g') - - # fix_print_with_import - print(db.list_schemas()) - # fix_print_with_import - print('==========') - - for row in db.list_geotables(): - # fix_print_with_import - print(row) - - # fix_print_with_import - print('==========') - - for row in db.get_table_indexes('trencin'): - # fix_print_with_import - print(row) - - # fix_print_with_import - print('==========') - - for row in db.get_table_constraints('trencin'): - # fix_print_with_import - print(row) - - # fix_print_with_import - print('==========') - - # fix_print_with_import - print(db.get_table_rows('trencin')) - - #for fld in db.get_table_metadata('trencin'): - # print fld - - #try: - # db.create_table('trrrr', [('id','serial'), ('test','text')]) - #except DbError, e: - # print e.message, e.query - + db = GeoDB(host='localhost',dbname='gis',user='gisak',passwd='g') + + # fix_print_with_import + print(db.list_schemas()) + # fix_print_with_import + print('==========') + + for row in db.list_geotables(): + # fix_print_with_import + print(row) + + # fix_print_with_import + print('==========') + + for row in db.get_table_indexes('trencin'): + # fix_print_with_import + print(row) + + # fix_print_with_import + print('==========') + + for row in db.get_table_constraints('trencin'): + # fix_print_with_import + print(row) + + # fix_print_with_import + print('==========') + + # fix_print_with_import + print(db.get_table_rows('trencin')) + + #for fld in db.get_table_metadata('trencin'): + # print fld + + #try: + # db.create_table('trrrr', [('id','serial'), ('test','text')]) + #except DbError, e: + # print e.message, e.query + # vim: noet ts=8 : diff --git a/dbConnection.py b/dbConnection.py index ec9d12a..11ad58a 100755 --- a/dbConnection.py +++ b/dbConnection.py @@ -9,180 +9,180 @@ from qgis.PyQt.QtWidgets import QAction -from . import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils class ConnectionManager(object): - SUPPORTED_CONNECTORS = ['postgis'] - MISSED_CONNECTORS = [] - - @classmethod - def initConnectionSupport(self): - conntypes = ConnectionManager.SUPPORTED_CONNECTORS - for c in conntypes: - try: - connector = self.getConnection( c ) - except ImportError as e: - module = e.args[0][ len("No module named "): ] - ConnectionManager.SUPPORTED_CONNECTORS.remove( c ) - ConnectionManager.MISSED_CONNECTORS.append( (c, module) ) - - return len(ConnectionManager.SUPPORTED_CONNECTORS) > 0 - - @classmethod - def getConnection(self, conntype, uri=None): - if not self.isSupported(conntype): - raise NotSupportedConnTypeException(conntype) - - # import the connector - exec( "from connectors import %s as connector" % conntype) - return connector.Connection(uri) if uri else connector.Connection - - @classmethod - def isSupported(self, conntype): - return conntype in ConnectionManager.SUPPORTED_CONNECTORS - - @classmethod - def getAvailableConnections(self, conntypes=None): - if conntypes == None: - conntypes = ConnectionManager.SUPPORTED_CONNECTORS - if not hasattr(conntypes, '__iter__'): - conntypes = [conntypes] - - connections = [] - for c in conntypes: - connection = self.getConnection( c ) - connections.extend( connection.getAvailableConnections() ) - return connections + SUPPORTED_CONNECTORS = ['postgis'] + MISSED_CONNECTORS = [] + + @classmethod + def initConnectionSupport(self): + conntypes = ConnectionManager.SUPPORTED_CONNECTORS + for c in conntypes: + try: + connector = self.getConnection( c ) + except ImportError as e: + module = e.args[0][ len("No module named "): ] + ConnectionManager.SUPPORTED_CONNECTORS.remove( c ) + ConnectionManager.MISSED_CONNECTORS.append( (c, module) ) + + return len(ConnectionManager.SUPPORTED_CONNECTORS) > 0 + + @classmethod + def getConnection(self, conntype, uri=None): + if not self.isSupported(conntype): + raise NotSupportedConnTypeException(conntype) + + # import the connector + exec( "from pgRoutingLayer.connectors import %s as connector" % conntype, globals(),globals()) + return connector.Connection(uri) if uri else connector.Connection + + @classmethod + def isSupported(self, conntype): + return conntype in ConnectionManager.SUPPORTED_CONNECTORS + + @classmethod + def getAvailableConnections(self, conntypes=None): + if conntypes == None: + conntypes = ConnectionManager.SUPPORTED_CONNECTORS + if not hasattr(conntypes, '__iter__'): + conntypes = [conntypes] + + connections = [] + for c in conntypes: + connection = self.getConnection( c ) + connections.extend( connection.getAvailableConnections() ) + return connections class NotSupportedConnTypeException(Exception): - def __init__(self, conntype): - self.msg = u"%s is not supported yet" % conntype + def __init__(self, conntype): + self.msg = u"%s is not supported yet" % conntype - def __str__(self): - return self.msg.encode('utf-8') + def __str__(self): + return self.msg.encode('utf-8') class DbError(Exception): - def __init__(self, errormsg, query=None): - self.msg = str( errormsg ) - self.query = str( query ) if query else None + def __init__(self, errormsg, query=None): + self.msg = str( errormsg ) + self.query = str( query ) if query else None - def __str__(self): - msg = self.msg.encode('utf-8') - if self.query != None: - msg += "\nQuery:\n" + self.query.encode('utf-8') - return msg + def __str__(self): + msg = self.msg.encode('utf-8') + if self.query != None: + msg += "\nQuery:\n" + self.query.encode('utf-8') + return msg class Connection(object): - def __init__(self, uri): - self.uri = uri + def __init__(self, uri): + self.uri = uri - @classmethod - def getTypeName(self): - pass + @classmethod + def getTypeName(self): + pass - @classmethod - def getTypeNameString(self): - pass + @classmethod + def getTypeNameString(self): + pass - @classmethod - def getProviderName(self): - pass + @classmethod + def getProviderName(self): + pass - @classmethod - def getSettingsKey(self): - pass + @classmethod + def getSettingsKey(self): + pass - @classmethod - def icon(self): - pass + @classmethod + def icon(self): + pass - @classmethod - def getAvailableConnections(self): - connections = [] + @classmethod + def getAvailableConnections(self): + connections = [] - settings = QSettings() - settings.beginGroup( "/%s/connections" % self.getSettingsKey() ) - keys = settings.childGroups() - for name in keys: - connections.append( Connection.ConnectionAction(name, self.getTypeName()) ) - settings.endGroup() + settings = QSettings() + settings.beginGroup( "/%s/connections" % self.getSettingsKey() ) + keys = settings.childGroups() + for name in keys: + connections.append( Connection.ConnectionAction(name, self.getTypeName()) ) + settings.endGroup() - return connections + return connections - def getURI(self): - # returns a new QgsDataSourceUri instance - return qgis.core.QgsDataSourceUri( self.uri.connectionInfo() ) + def getURI(self): + # returns a new QgsDataSourceUri instance + return QgsDataSourceUri( self.uri.connectionInfo() ) - def getAction(self, parent=None): - return Connection.ConnectionAction(self.uri.database(), self.getTypeName(), parent) + def getAction(self, parent=None): + return Connection.ConnectionAction(self.uri.database(), self.getTypeName(), parent) - class ConnectionAction(QAction): - def __init__(self, text, conntype, parent=None): - self.type = conntype - icon = ConnectionManager.getConnection(self.type).icon() - QAction.__init__(self, icon, text, parent) + class ConnectionAction(QAction): + def __init__(self, text, conntype, parent=None): + self.type = conntype + icon = ConnectionManager.getConnection(self.type).icon() + QAction.__init__(self, icon, text, parent) - def connect(self): - selected = self.text() - conn = ConnectionManager.getConnection( self.type ).connect( selected, self.parent() ) + def connect(self): + selected = self.text() + conn = ConnectionManager.getConnection( self.type ).connect( selected, self.parent() ) - # set as default in QSettings - settings = QSettings() - settings.setValue( "/%s/connections/selected" % conn.getSettingsKey(), selected ) + # set as default in QSettings + settings = QSettings() + settings.setValue( "/%s/connections/selected" % conn.getSettingsKey(), selected ) - return conn + return conn class TableAttribute(object): - pass + pass class TableConstraint(object): - """ class that represents a constraint of a table (relation) """ - - TypeCheck, TypeForeignKey, TypePrimaryKey, TypeUnique = list(range(4)) - types = { "c" : TypeCheck, "f" : TypeForeignKey, "p" : TypePrimaryKey, "u" : TypeUnique } - - on_action = { "a" : "NO ACTION", "r" : "RESTRICT", "c" : "CASCADE", "n" : "SET NULL", "d" : "SET DEFAULT" } - match_types = { "u" : "UNSPECIFIED", "f" : "FULL", "p" : "PARTIAL" } + """ class that represents a constraint of a table (relation) """ - pass + TypeCheck, TypeForeignKey, TypePrimaryKey, TypeUnique = list(range(4)) + types = { "c" : TypeCheck, "f" : TypeForeignKey, "p" : TypePrimaryKey, "u" : TypeUnique } + + on_action = { "a" : "NO ACTION", "r" : "RESTRICT", "c" : "CASCADE", "n" : "SET NULL", "d" : "SET DEFAULT" } + match_types = { "u" : "UNSPECIFIED", "f" : "FULL", "p" : "PARTIAL" } + + pass class TableIndex(object): - pass + pass class TableTrigger(object): - # Bits within tgtype (pg_trigger.h) - TypeRow = (1 << 0) # row or statement - TypeBefore = (1 << 1) # before or after - # events: one or more - TypeInsert = (1 << 2) - TypeDelete = (1 << 3) - TypeUpdate = (1 << 4) - TypeTruncate = (1 << 5) + # Bits within tgtype (pg_trigger.h) + TypeRow = (1 << 0) # row or statement + TypeBefore = (1 << 1) # before or after + # events: one or more + TypeInsert = (1 << 2) + TypeDelete = (1 << 3) + TypeUpdate = (1 << 4) + TypeTruncate = (1 << 5) - pass + pass class TableRule(object): - pass + pass class TableField(object): - def is_null_txt(self): - if self.is_null: - return "NULL" - else: - return "NOT NULL" - - def field_def(self, db): - """ return field definition as used for CREATE TABLE or ALTER TABLE command """ - data_type = self.data_type if (not self.modifier or self.modifier < 0) else "%s(%d)" % (self.data_type, self.modifier) - txt = "%s %s %s" % (db._quote(self.name), data_type, self.is_null_txt()) - if self.default and len(self.default) > 0: - txt += " DEFAULT %s" % self.default - return txt + def is_null_txt(self): + if self.is_null: + return "NULL" + else: + return "NOT NULL" + + def field_def(self, db): + """ return field definition as used for CREATE TABLE or ALTER TABLE command """ + data_type = self.data_type if (not self.modifier or self.modifier < 0) else "%s(%d)" % (self.data_type, self.modifier) + txt = "%s %s %s" % (db._quote(self.name), data_type, self.is_null_txt()) + if self.default and len(self.default) > 0: + txt += " DEFAULT %s" % self.default + return txt diff --git a/functions/FunctionBase.py b/functions/FunctionBase.py index d20671d..e1b2ecc 100755 --- a/functions/FunctionBase.py +++ b/functions/FunctionBase.py @@ -1,10 +1,10 @@ from builtins import str from builtins import object -from qgis.core import (QgsMessageLog, Qgis, QgsGeometry) +from qgis.core import (QgsMessageLog, Qgis, QgsGeometry,QgsWkbTypes) from qgis.gui import QgsRubberBand from qgis.PyQt.QtGui import QColor -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils class FunctionBase(object): @@ -45,22 +45,27 @@ def getControlNames(self, version): @classmethod def isEdgeBase(self): + ''' checks if EdgeBase is set. ''' return self.exportEdgeBase @classmethod def canExport(self): + ''' checks if exportButton is set ''' return self.exportButton @classmethod def canExportMerged(self): + ''' checks if exportMergeButton is set. ''' return self.exportMergeButton @classmethod def isSupportedVersion(self, version): + ''' returns true if version is between 2 and 3.0 ''' return version >= 2.0 and version < 3.0 @classmethod def whereClause(self, table, geometry, bbox): + ''' returns where clause for sql parameterising ''' if bbox == ' ': return ' ' else: @@ -82,6 +87,7 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): pass def getJoinResultWithEdgeTable(self, args): + '''returns a query which joins edge_table with result based on edge.id''' args['result_query'] = self.getQuery(args) query = """ @@ -101,6 +107,7 @@ def getJoinResultWithEdgeTable(self, args): def getExportOneSourceOneTargetMergeQuery(self, args): + ''' returns merge query for one source and one target ''' args['result_query'] = self.getQuery(args) args['with_geom_query'] = """ @@ -139,6 +146,7 @@ def getExportOneSourceOneTargetMergeQuery(self, args): def getExportManySourceManyTargetMergeQuery(self, args): + ''' returns merge query for many source and many target ''' args['result_query'] = self.getQuery(args) args['with_geom_query'] = """ @@ -184,6 +192,7 @@ def getExportManySourceManyTargetMergeQuery(self, args): def drawManyPaths(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draws multi line string on the mapCanvas. ''' resultPathsRubberBands = canvasItemList['paths'] rubberBand = None cur_path_id = str(-1) + "," + str(-1) @@ -218,11 +227,11 @@ def drawManyPaths(self, rows, con, args, geomType, canvasItemList, mapCanvas): assert row2, "Invalid result geometry. (path_id:%(result_path_id)s, node_id:%(result_node_id)d, edge_id:%(result_edge_id)d)" % args geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == Qgis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: rubberBand.addPoint(pt) - elif geom.wkbType() == Qgis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): rubberBand.addPoint(pt) @@ -232,8 +241,9 @@ def drawManyPaths(self, rows, con, args, geomType, canvasItemList, mapCanvas): def drawOnePath(self, rows, con, args, geomType, canvasItemList, mapCanvas): - resultPathRubberBand = canvasItemList['path'] - for row in rows: + ''' draws line string on the mapCanvas. ''' + resultPathRubberBand = canvasItemList['path'] + for row in rows: cur2 = con.cursor() args['result_node_id'] = row[1] args['result_edge_id'] = row[2] @@ -253,11 +263,11 @@ def drawOnePath(self, rows, con, args, geomType, canvasItemList, mapCanvas): assert row2, "Invalid result geometry. (node_id:%(result_node_id)d, edge_id:%(result_edge_id)d)" % args geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == Qgis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: resultPathRubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): resultPathRubberBand.addPoint(pt) diff --git a/functions/Test_FunctionBase.py b/functions/Test_FunctionBase.py deleted file mode 100644 index 8efbbc0..0000000 --- a/functions/Test_FunctionBase.py +++ /dev/null @@ -1,82 +0,0 @@ -import unittest -import FunctionBase as FB - - - -class TestStringMethods(unittest.TestCase): - def test_getJoinResultWithEdgeTable(self): - args = {'geometry' : 'test_geom','source': 'test_source','startpoint' : 10,'id':'test_id','edge_table':'test_Table','target':100,'endpoint':90} - - expected_sql = """ - WITH - result AS ( ) - SELECT - CASE - WHEN result._node = test_Table.test_source - THEN test_Table.test_geom - ELSE ST_Reverse(test_Table.test_geom) - END AS path_geom, - result.*, test_Table.* - FROM test_Table JOIN result - ON test_Table.test_id = result._edge ORDER BY result.seq - """ - self.maxDiff = None - - self.assertEqual(FB.getJoinResultWithEdgeTable(args),expected_sql) - - def test_getExportManySourceManyTargetMergeQuery(self): - - args = {'geometry' : 'test_geom','source': 'test_source','startpoint' : 10,'id':'test_id','edge_table':'test_Table','target':100,'endpoint':90} - expected_sql = """WITH - result AS ( ), - with_geom AS ( - SELECT - seq, result.path_name, - CASE - WHEN result._node = test_Table.test_source - THEN test_Table.test_geom - ELSE ST_Reverse(test_Table.test_geom) - END AS path_geom - FROM test_Table JOIN result - ON test_Table.test_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, _start_vid, _end_vid, - 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, _start_vid, _end_vid - ORDER BY _start_vid, _end_vid ) - SELECT row_number() over() as seq, - path_name, _start_vid, _end_vid, agg_cost, _nodes, _edges, - path_geom AS path_geom FROM aggregates JOIN one_geom - USING (path_name) - """ - self.maxDiff = None - - self.assertEqual(FB.getExportManySourceManyTargetMergeQuery(args),expected_sql) - - - - - - - - - - - - -if __name__ == '__main__': - - - unittest.main() - diff --git a/functions/__init__.py b/functions/__init__.py old mode 100644 new mode 100755 diff --git a/functions/alphashape.py b/functions/alphashape.py index 5fb954c..ccc1db1 100755 --- a/functions/alphashape.py +++ b/functions/alphashape.py @@ -1,20 +1,22 @@ from __future__ import absolute_import from builtins import str -from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsPoint, QgsMessageLog +from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsPoint, Qgis, QgsProject from qgis.gui import QgsRubberBand import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): - + @classmethod def getName(self): + ''' returns Function name. ''' return 'alphashape' - + @classmethod def getControlNames(self, version): + ''' returns control names for this function. ''' self.version = version return self.commonControls + self.commonBoxes + [ 'labelId', 'lineEditId', @@ -27,7 +29,7 @@ def getControlNames(self, version): 'labelAlpha', 'lineEditAlpha', 'checkBoxDirected', 'checkBoxHasReverseCost' ] - + @classmethod def canExportMerged(self): return False @@ -37,6 +39,7 @@ def prepare(self, canvasItemList): resultAreaRubberBand.reset(Utils.getRubberBandType(True)) def getQuery(self, args): + ''' returns the sql query for pgr_alphashape ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) if args['version'] < 2.1: return """ @@ -61,7 +64,7 @@ def getQuery(self, args): ) SELECT * FROM node$$::text) """ % args - + # V21.+ has pgr_drivingDistance with big int # and pgr_alphaShape has an alpha value args['alpha'] = ', ' + str(args['alpha']) @@ -87,7 +90,7 @@ def getQuery(self, args): ) SELECT * FROM node$$::text%(alpha)s) """ % args - + def getExportQuery(self, args): @@ -141,26 +144,28 @@ def getExportQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the resulting polygon. ''' + resultAreaRubberBand = canvasItemList['area'] trans = None - if mapCanvas.hasCrsTransformEnabled(): - canvasCrs = Utils.getDestinationCrs(mapCanvas) - layerCrs = QgsCoordinateReferenceSystem() - Utils.createFromSrid(layerCrs, args['srid']) - trans = QgsCoordinateTransform(layerCrs, canvasCrs) - + + canvasCrs = Utils.getDestinationCrs(mapCanvas) + layerCrs = QgsCoordinateReferenceSystem() + Utils.createFromSrid(layerCrs, args['srid']) + trans = QgsCoordinateTransform(layerCrs, canvasCrs,QgsProject.instance()) + # return columns are 'x', 'y' for row in rows: x = row[0] y = row[1] if args['version'] > 2.0 and ((x is None) or (y is None)): - Utils.logMessage(u'Alpha shape result geometry is MultiPolygon or has holes.\nPlease click [Export] button to see complete result.', level=QgsMessageLog.WARNING) + Utils.logMessage(u'Alpha shape result geometry is MultiPolygon or has holes.\nPlease click [Export] button to see complete result.', level=Qgis.Warning) return pt = QgsPoint(x, y) if trans: - pt = trans.transform(pt) - + pt = trans.transform(pt.x(),pt.y()) + resultAreaRubberBand.addPoint(pt) - + def __init__(self, ui): FunctionBase.__init__(self, ui) diff --git a/functions/astar.py b/functions/astar.py index 101b797..231495a 100755 --- a/functions/astar.py +++ b/functions/astar.py @@ -1,17 +1,19 @@ from __future__ import absolute_import -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): - version = 2.0 + version = 3.0 @classmethod def getName(self): + ''' returns Function name. ''' return 'astar' @classmethod def getControlNames(self, version): + ''' returns control names for this function. ''' self.version = version if self.version < 2.4: return self.commonControls + self.commonBoxes + self.astarControls + [ @@ -36,6 +38,7 @@ def prepare(self, canvasItemList): def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_astar ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) if self.version < 2.3: return """ @@ -102,6 +105,7 @@ def getExportMergeQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result. ''' if self.version < 2.4: self.drawOnePath(rows, con, args, geomType, canvasItemList, mapCanvas) else: diff --git a/functions/bdAstar.py b/functions/bdAstar.py index f3c7af6..9b90d1b 100755 --- a/functions/bdAstar.py +++ b/functions/bdAstar.py @@ -1,5 +1,5 @@ from __future__ import absolute_import -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): @@ -8,10 +8,12 @@ class Function(FunctionBase): @classmethod def getName(self): + ''' returns Function name. ''' return 'bdAstar' @classmethod def getControlNames(self, version): + ''' returns control names. ''' self.version = version if self.version < 2.5: return self.commonControls + self.commonBoxes + self.astarControls + [ @@ -36,6 +38,7 @@ def prepare(self, canvasItemList): def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_bdAstar ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) if self.version < 2.5: return """ @@ -85,6 +88,7 @@ def getExportMergeQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draws the result ''' if self.version < 2.5: self.drawOnePath(rows, con, args, geomType, canvasItemList, mapCanvas) else: diff --git a/functions/bdDijkstra.py b/functions/bdDijkstra.py index 05ddd2c..2ca5e9d 100755 --- a/functions/bdDijkstra.py +++ b/functions/bdDijkstra.py @@ -1,5 +1,5 @@ from __future__ import absolute_import -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): @@ -8,10 +8,12 @@ class Function(FunctionBase): @classmethod def getName(self): + ''' returns Function name. ''' return 'bdDijkstra' @classmethod def getControlNames(self, version): + ''' returns control names. ''' self.version = version if self.version < 2.5: return self.commonControls + self.commonBoxes + [ @@ -36,6 +38,7 @@ def prepare(self, canvasItemList): def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_bdDijkstra ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) if self.version < 2.4: return """ @@ -73,7 +76,7 @@ def getQuery(self, args): %(source)s AS source, %(target)s AS target, %(cost)s AS cost - %(reverse_cost)s, + %(reverse_cost)s FROM %(edge_table)s %(where_clause)s', array[%(source_ids)s]::BIGINT[], array[%(target_ids)s]::BIGINT[], %(directed)s) @@ -90,6 +93,7 @@ def getExportMergeQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' if self.version < 2.5: self.drawOnePath(rows, con, args, geomType, canvasItemList, mapCanvas) else: diff --git a/functions/dijkstra.py b/functions/dijkstra.py index 2f42d3a..b377d91 100755 --- a/functions/dijkstra.py +++ b/functions/dijkstra.py @@ -1,5 +1,5 @@ from __future__ import absolute_import -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): @@ -8,11 +8,13 @@ class Function(FunctionBase): @classmethod def getName(self): + ''' returns Function name. ''' return 'dijkstra' @classmethod def getControlNames(self, version): + ''' returns control names. ''' self.version = version if self.version < 2.1: # version 2.0 has only one to one @@ -39,6 +41,7 @@ def prepare(self, canvasItemList): def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_bdDijkstra ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) if self.version < 2.1: return """ @@ -82,6 +85,7 @@ def getExportMergeQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' if self.version < 2.1: self.drawOnePath(rows, con, args, geomType, canvasItemList, mapCanvas) else: diff --git a/functions/dijkstraCost.py b/functions/dijkstraCost.py index f08f1b6..70e951f 100755 --- a/functions/dijkstraCost.py +++ b/functions/dijkstraCost.py @@ -1,21 +1,23 @@ from __future__ import absolute_import from builtins import str from qgis.PyQt.QtCore import QSizeF, QPointF -from qgis.PyQt.QtGui import QColor -from qgis.core import (QgsGeometry, QGis) -from qgis.gui import (QgsRubberBand, QgsTextAnnotationItem) +from qgis.PyQt.QtGui import QColor, QTextDocument +from qgis.core import QgsGeometry, Qgis, QgsTextAnnotation, QgsWkbTypes, QgsAnnotation +from qgis.gui import QgsRubberBand, QgsMapCanvasAnnotationItem import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): @classmethod def getName(self): - return 'pgr_dijkstraCost' + ''' returns Function name. ''' + return 'dijkstraCost' @classmethod def isSupportedVersion(self, version): + ''' Checks supported version ''' # valid starting pgr v2.1 return version >= 2.1 @@ -29,15 +31,16 @@ def canExportMerged(self): return False - + def prepare(self, canvasItemList): resultNodesTextAnnotations = canvasItemList['annotations'] for anno in resultNodesTextAnnotations: anno.setVisible(False) canvasItemList['annotations'] = [] - + def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_dijkstra ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) return """ SELECT row_number() over() AS seq, @@ -58,7 +61,7 @@ def getQuery(self, args): def getExportQuery(self, args): args['result_query'] = self.getQuery(args) - args['vertex_table'] = """ + args['vertex_table'] = """ %(edge_table)s_vertices_pgr """ % args @@ -76,6 +79,7 @@ def getExportQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' resultPathsRubberBands = canvasItemList['paths'] rubberBand = None cur_path_id = -1 @@ -96,7 +100,7 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): rubberBand.setWidth(4) if args['result_cost'] != -1: query2 = """ - SELECT ST_AsText( ST_MakeLine( + SELECT ST_AsText( ST_MakeLine( (SELECT the_geom FROM %(edge_table)s_vertices_pgr WHERE id = %(result_source_id)d), (SELECT the_geom FROM %(edge_table)s_vertices_pgr WHERE id = %(result_target_id)d) )) @@ -108,11 +112,11 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): assert row2, "Invalid result geometry. (path_id:%(result_path_id)d, saource_id:%(result_source_id)d, target_id:%(result_target_id)d)" % args geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: rubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): rubberBand.addPoint(pt) @@ -142,16 +146,14 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): geom = QgsGeometry().fromWkt(str(row2[0])) pt = geom.asPoint() textDocument = QTextDocument("%(result_target_id)d:%(result_cost)f" % args) - textAnnotation = QgsTextAnnotationItem(mapCanvas) + textAnnotation = QgsTextAnnotation() textAnnotation.setMapPosition(geom.asPoint()) textAnnotation.setFrameSize(QSizeF(textDocument.idealWidth(), 20)) - textAnnotation.setOffsetFromReferencePoint(QPointF(20, -40)) + textAnnotation.setFrameOffsetFromReferencePoint(QPointF(20, -40)) textAnnotation.setDocument(textDocument) - textAnnotation.update() + QgsMapCanvasAnnotationItem(textAnnotation, mapCanvas) resultNodesTextAnnotations.append(textAnnotation) - - def __init__(self, ui): FunctionBase.__init__(self, ui) diff --git a/functions/drivingDistance.py b/functions/drivingDistance.py index e191423..4ec02aa 100755 --- a/functions/drivingDistance.py +++ b/functions/drivingDistance.py @@ -1,21 +1,23 @@ from __future__ import absolute_import from builtins import str from qgis.PyQt.QtCore import Qt -#from PyQt4.QtGui import * -from qgis.core import QgsGeometry, QgsPoint +from qgis.PyQt.QtGui import * +from qgis.core import QgsGeometry, QgsPointXY from qgis.gui import QgsVertexMarker import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): @classmethod def getName(self): + ''' returns Function name. ''' return '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 + [ @@ -35,6 +37,7 @@ def prepare(self, canvasItemList): 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 """ @@ -93,6 +96,7 @@ def getExportMergeQuery(self, args): return self.getJoinResultWithEdgeTable(args) def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' resultNodesVertexMarkers = canvasItemList['markers'] table = """%(edge_table)s_vertices_pgr""" % args srid, geomType = Utils.getSridAndGeomType(con, table, 'the_geom') @@ -123,7 +127,7 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): vertexMarker.setColor(Qt.red) vertexMarker.setPenWidth(2) vertexMarker.setIconSize(5) - vertexMarker.setCenter(QgsPoint(pt)) + vertexMarker.setCenter(QgsPointXY(pt)) resultNodesVertexMarkers.append(vertexMarker) def __init__(self, ui): diff --git a/functions/kdijkstra_path.py b/functions/kdijkstra_path.py deleted file mode 100755 index ca893c8..0000000 --- a/functions/kdijkstra_path.py +++ /dev/null @@ -1,154 +0,0 @@ -from __future__ import absolute_import -#from PyQt4.QtCore import * -from builtins import str -from qgis.PyQt.QtGui import QColor -from qgis.core import QgsGeometry, QGis -from qgis.gui import QgsRubberBand -import psycopg2 -from .. import pgRoutingLayer_utils as Utils -from .FunctionBase import FunctionBase - -class Function(FunctionBase): - - @classmethod - def getName(self): - return 'kdijkstra(path)' - - @classmethod - def isSupportedVersion(self, version): - # Deprecated on version 2.2 - return version >= 2.0 and version < 2.2 - - - @classmethod - def getControlNames(self, version): - # version 2.0 has only one to many - return self.commonControls + self.commonBoxes + [ - 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', - 'labelTargetIds', 'lineEditTargetIds', 'buttonSelectTargetIds', - ] - - - def prepare(self, canvasItemList): - resultPathsRubberBands = canvasItemList['paths'] - for path in resultPathsRubberBands: - path.reset(Utils.getRubberBandType(False)) - canvasItemList['paths'] = [] - - def getQuery(self, args): - args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) - return """ - SELECT seq, - id1 AS _path, id2 AS _node, id3 AS _edge, cost AS _cost FROM pgr_kdijkstraPath(' - 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, array[%(target_ids)s], %(directed)s, %(has_reverse_cost)s)""" % args - - def getExportQuery(self, args): - return self.getJoinResultWithEdgeTable(args) - - def getExportMergeQuery(self, args): - args['result_query'] = self.getQuery(args) - - args['with_geom_query'] = """ - SELECT - seq, _path, - CASE - WHEN result._node = %(edge_table)s.%(source)s - THEN %(edge_table)s.%(geometry)s - ELSE ST_Reverse(%(edge_table)s.%(geometry)s) - END AS path_geom - FROM %(edge_table)s JOIN result - ON %(edge_table)s.%(id)s = result._edge - """ % args - - args['one_geom_query'] = """ - SELECT _path, ST_LineMerge(ST_Union(path_geom)) AS path_geom - FROM with_geom - GROUP BY _path - ORDER BY _path - """ % args - - args['aggregates_query'] = """SELECT - _path, - SUM(_cost) AS agg_cost, - array_agg(_node ORDER BY seq) AS _nodes, - array_agg(_edge ORDER BY seq) AS _edges - FROM result - GROUP BY _path - """ - - query = """ - WITH - result AS ( %(result_query)s ), - with_geom AS ( %(with_geom_query)s ), - one_geom AS ( %(one_geom_query)s ), - aggregates AS ( %(aggregates_query)s ) - SELECT row_number() over() as seq, - _path, _nodes, _edges, agg_cost, - path_geom FROM aggregates JOIN one_geom - USING (_path) - """ % args - return query - - def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): - resultPathsRubberBands = canvasItemList['paths'] - rubberBand = None - cur_path_id = -1 - for row in rows: - cur2 = con.cursor() - args['result_path_id'] = row[1] - args['result_node_id'] = row[2] - args['result_edge_id'] = row[3] - args['result_cost'] = row[4] - if args['result_path_id'] != cur_path_id: - cur_path_id = args['result_path_id'] - if rubberBand: - resultPathsRubberBands.append(rubberBand) - rubberBand = None - - rubberBand = QgsRubberBand(mapCanvas, Utils.getRubberBandType(False)) - rubberBand.setColor(QColor(255, 0, 0, 128)) - rubberBand.setWidth(4) - - if args['result_edge_id'] != -1: - query2 = """ - SELECT ST_AsText(%(transform_s)s%(geometry)s%(transform_e)s) FROM %(edge_table)s - WHERE %(source)s = %(result_node_id)d AND %(id)s = %(result_edge_id)d - UNION - SELECT ST_AsText(%(transform_s)sST_Reverse(%(geometry)s)%(transform_e)s) FROM %(edge_table)s - WHERE %(target)s = %(result_node_id)d AND %(id)s = %(result_edge_id)d; - """ % args - ## pgRouting <= 2.0.0rc1 - #query2 = """ - # SELECT ST_AsText(%(transform_s)sST_Reverse(%(geometry)s)%(transform_e)s) FROM %(edge_table)s - # WHERE %(source)s = %(result_node_id)d AND %(id)s = %(result_edge_id)d - # UNION - # SELECT ST_AsText(%(transform_s)s%(geometry)s%(transform_e)s) FROM %(edge_table)s - # WHERE %(target)s = %(result_node_id)d AND %(id)s = %(result_edge_id)d; - #""" % args - ##Utils.logMessage(query2) - cur2.execute(query2) - row2 = cur2.fetchone() - ##Utils.logMessage(str(row2[0])) - assert row2, "Invalid result geometry. (path_id:%(result_path_id)d, node_id:%(result_node_id)d, edge_id:%(result_edge_id)d)" % args - - geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: - for line in geom.asMultiPolyline(): - for pt in line: - rubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: - for pt in geom.asPolyline(): - rubberBand.addPoint(pt) - - if rubberBand: - resultPathsRubberBands.append(rubberBand) - rubberBand = None - - def __init__(self, ui): - FunctionBase.__init__(self, ui) diff --git a/functions/ksp.py b/functions/ksp.py index c7db894..004fae9 100755 --- a/functions/ksp.py +++ b/functions/ksp.py @@ -1,53 +1,42 @@ from __future__ import absolute_import -#from PyQt4.QtCore import * +from qgis.PyQt.QtCore import * from builtins import str from qgis.PyQt.QtGui import QColor -from qgis.core import QGis, QgsGeometry +from qgis.core import Qgis, QgsGeometry, QgsWkbTypes from qgis.gui import QgsRubberBand import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): version = 2.0 - + @classmethod def getName(self): + ''' returns Function name. ''' return 'ksp' - + @classmethod def getControlNames(self, version): + ''' returns control names. ''' # function pgr_ksp(text,integer,integer,integer, boolean) # boolean is has_rcost # only works for directed graph self.version = version if (self.version < 2.1): - return [ - 'labelId', 'lineEditId', - 'labelSource', 'lineEditSource', - 'labelTarget', 'lineEditTarget', - 'labelCost', 'lineEditCost', - 'labelReverseCost', 'lineEditReverseCost', + return self.commonControls + self.commonBoxes + [ 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', 'labelTargetId', 'lineEditTargetId', 'buttonSelectTargetId', - 'labelPaths', 'lineEditPaths', - 'checkBoxHasReverseCost' + 'labelPaths', 'lineEditPaths' ] else: # function pgr_ksp(text,bigint,bigint,integer,boolean,boolean) - return [ - 'labelId', 'lineEditId', - 'labelSource', 'lineEditSource', - 'labelTarget', 'lineEditTarget', - 'labelCost', 'lineEditCost', - 'labelReverseCost', 'lineEditReverseCost', + return self.commonControls + self.commonBoxes + [ 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', 'labelTargetId', 'lineEditTargetId', 'buttonSelectTargetId', 'labelPaths', 'lineEditPaths', - 'checkBoxDirected', - 'checkBoxHasReverseCost', 'checkBoxHeapPaths' ] @@ -58,6 +47,8 @@ def prepare(self, canvasItemList): canvasItemList['paths'] = [] def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_bdDijkstra ''' + args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) if (self.version < 2.1): return """ SELECT @@ -71,8 +62,8 @@ def getQuery(self, args): %(cost)s::float8 AS cost %(reverse_cost)s FROM %(edge_table)s - WHERE %(edge_table)s.%(geometry)s && %(BBOX)s', - %(source_id)s, %(target_id)s, %(paths)s, %(has_reverse_cost)s)""" % args + %(where_clause)s', + %(source_id)s, %(target_id)s, %(paths)s, %(has_reverse_cost)s)""" % args else: return """ SELECT seq, @@ -89,7 +80,7 @@ def getQuery(self, args): %(cost)s AS cost %(reverse_cost)s FROM %(edge_table)s - WHERE %(edge_table)s.%(geometry)s && %(BBOX)s', + %(where_clause)s', %(source_id)s, %(target_id)s, %(paths)s, %(directed)s, %(heap_paths)s)""" % args @@ -103,7 +94,7 @@ def getExportMergeQuery(self, args): args['result_query'] = self.getQuery(args) args['with_geom_query'] = """ - SELECT + SELECT seq, _route, CASE WHEN result._node = %(edge_table)s.%(source)s @@ -111,7 +102,7 @@ def getExportMergeQuery(self, args): ELSE ST_Reverse(%(edge_table)s.%(geometry)s) END AS path_geom FROM %(edge_table)s JOIN result - ON %(edge_table)s.%(id)s = result._edge + ON %(edge_table)s.%(id)s = result._edge """ % args args['one_geom_query'] = """ @@ -146,7 +137,7 @@ def getExportMergeQuery(self, args): args['result_query'] = self.getQuery(args) args['with_geom_query'] = """ - SELECT + SELECT seq, result.path_name, CASE WHEN result._node = %(edge_table)s.%(source)s @@ -154,7 +145,7 @@ def getExportMergeQuery(self, args): ELSE ST_Reverse(%(edge_table)s.%(geometry)s) END AS path_geom FROM %(edge_table)s JOIN result - ON %(edge_table)s.%(id)s = result._edge + ON %(edge_table)s.%(id)s = result._edge """ % args args['one_geom_query'] = """ @@ -180,7 +171,7 @@ def getExportMergeQuery(self, args): aggregates AS ( %(aggregates_query)s ) SELECT row_number() over() as seq, _path_id, path_name, _nodes, _edges, agg_cost, - path_geom FROM aggregates JOIN one_geom + path_geom FROM aggregates JOIN one_geom USING (path_name) ORDER BY _path_id """ % args @@ -189,6 +180,7 @@ def getExportMergeQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' resultPathsRubberBands = canvasItemList['paths'] rubberBand = None cur_route_id = -1 @@ -232,11 +224,11 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): assert row2, "Invalid result geometry. (route_id:%(result_route_id)d, node_id:%(result_node_id)d, edge_id:%(result_edge_id)d)" % args geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: rubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): rubberBand.addPoint(pt) diff --git a/functions/trsp_edge.py b/functions/trsp_edge.py index 6be39fd..1adc589 100755 --- a/functions/trsp_edge.py +++ b/functions/trsp_edge.py @@ -1,38 +1,42 @@ from __future__ import absolute_import -#from PyQt4.QtCore import * -#from PyQt4.QtGui import * +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import * from builtins import str -from qgis.core import QGis, QgsGeometry +from qgis.core import Qgis, QgsGeometry, QgsWkbTypes from qgis.gui import QgsRubberBand import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): - + @classmethod def getName(self): + ''' returns Function name. ''' return 'trsp(edge)' - + @classmethod def getControlNames(self, version): + ''' returns control names. ''' return self.commonControls + self.commonBoxes + [ 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', 'labelSourcePos', 'lineEditSourcePos', 'labelTargetId', 'lineEditTargetId', 'buttonSelectTargetId', 'labelTargetPos', 'lineEditTargetPos', - 'labelTurnRestrictSql', 'plainTextEditTurnRestrictSql' ] + 'labelTurnRestrictSql', 'plainTextEditTurnRestrictSql' ] + - @classmethod def isEdgeBase(self): return True - + def prepare(self, canvasItemList): resultPathRubberBand = canvasItemList['path'] resultPathRubberBand.reset(Utils.getRubberBandType(False)) - + def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_trsp_edge ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) return """ SELECT seq, id1 AS _node, id2 AS _edge, cost AS _cost FROM pgr_trsp(' @@ -42,7 +46,7 @@ def getQuery(self, args): FROM %(edge_table)s %(where_clause)s', %(source_id)s, %(source_pos)s, %(target_id)s, %(target_pos)s, %(directed)s, %(has_reverse_cost)s, %(turn_restrict_sql)s)""" % args - + def getExportQuery(self, args): args['result_query'] = 'result AS (' + self.getQuery(args) + ')' @@ -50,14 +54,14 @@ def getExportQuery(self, args): args['with_geom'] = """ with_geom AS ( SELECT - lead(_node) over(), result.*, %(edge_table)s.* + lead(_node) over(), result.*, %(edge_table)s.* FROM %(edge_table)s JOIN result - ON edge_table.id = result._edge ORDER BY result.seq)""" % args + ON %(edge_table)s.%(id)s = result._edge ORDER BY result.seq)""" % args args['first_row_split'] = self.getRowSplit(args, 'first') args['last_row_split'] = self.getRowSplit(args, 'last') - args['intermediate_rows'] = """ intermediate_rows AS (SELECT + args['intermediate_rows'] = """ intermediate_rows AS (SELECT CASE WHEN result._node = %(edge_table)s.%(source)s THEN %(edge_table)s.%(geometry)s @@ -88,14 +92,14 @@ def getExportMergeQuery(self, args): args['with_geom'] = """ with_geom AS ( SELECT - lead(_node) over(), result.*, %(edge_table)s.* + lead(_node) over(), result.*, %(edge_table)s.* FROM %(edge_table)s JOIN result - ON edge_table.id = result._edge ORDER BY result.seq)""" % args + ON %(edge_table)s.%(id)s = result._edge ORDER BY result.seq)""" % args args['first_row_split'] = self.getRowSplit(args, 'first') args['last_row_split'] = self.getRowSplit(args, 'last') - args['intermediate_rows'] = """ intermediate_rows AS (SELECT + args['intermediate_rows'] = """ intermediate_rows AS (SELECT CASE WHEN result._node = %(edge_table)s.%(source)s THEN %(edge_table)s.%(geometry)s @@ -127,6 +131,7 @@ def getExportMergeQuery(self, args): """ % args def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' resultPathRubberBand = canvasItemList['path'] i = 0 count = len(rows) @@ -136,7 +141,7 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): args['result_node_id'] = row[1] args['result_edge_id'] = row[2] args['result_cost'] = row[3] - + if i == 0 and args['result_node_id'] == -1: args['result_next_node_id'] = rows[i + 1][1] query2 = """ @@ -165,24 +170,24 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): SELECT ST_AsText(%(transform_s)sST_Reverse(%(geometry)s)%(transform_e)s) FROM %(edge_table)s WHERE %(target)s = %(result_node_id)d AND %(id)s = %(result_edge_id)d; """ % args - + ##Utils.logMessage(query2) cur2.execute(query2) row2 = cur2.fetchone() ##Utils.logMessage(str(row2[0])) assert row2, "Invalid result geometry. (node_id:%(result_node_id)d, edge_id:%(result_edge_id)d)" % args - + geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: resultPathRubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): resultPathRubberBand.addPoint(pt) - + i = i + 1 - + def getRowSplit(self, args, which): # PRIVATE method # upper case for localy defined string values @@ -209,7 +214,7 @@ def getRowSplit(self, args, which): ST_LineInterpolatePoint(%(geometry)s, %(POSITION)s)) ELSE ST_reverse( ST_split( ST_Snap( %(geometry)s, ST_LineInterpolatePoint(%(geometry)s, %(POSITION)s), 0.00001), - ST_LineInterpolatePoint(%(geometry)s, %(POSITION)s))) + ST_LineInterpolatePoint(%(geometry)s, %(POSITION)s))) END AS line_geom, st_length(%(geometry)s) AS length, _cost diff --git a/functions/trsp_vertex.py b/functions/trsp_vertex.py index 1aa63b1..02067e5 100755 --- a/functions/trsp_vertex.py +++ b/functions/trsp_vertex.py @@ -1,21 +1,23 @@ from __future__ import absolute_import -#from PyQt4.QtCore import * -#from PyQt4.QtGui import * +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * from builtins import str -from qgis.core import QGis, QgsGeometry +from qgis.core import Qgis, QgsGeometry, QgsWkbTypes from qgis.gui import QgsRubberBand import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): @classmethod def getName(self): + ''' returns Function name. ''' return 'trsp(vertex)' @classmethod def getControlNames(self, version): + ''' returns control names. ''' return self.commonControls + self.commonBoxes + [ 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', 'labelTargetId', 'lineEditTargetId', 'buttonSelectTargetId', @@ -26,13 +28,15 @@ def getControlNames(self, version): ] def isSupportedVersion(self, version): - return version >= 2.0 and version < 3.0 + ''' checks supported version. ''' + return version >= 2.0 def prepare(self, canvasItemList): resultPathRubberBand = canvasItemList['path'] resultPathRubberBand.reset(Utils.getRubberBandType(False)) def getQuery(self, args): + ''' returns the sql query in required signature format of trsp_vertex ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) return """ SELECT seq, id1 AS _node, id2 AS _edge, cost AS _cost FROM pgr_trsp(' @@ -55,6 +59,7 @@ def getExportMergeQuery(self, args): def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' resultPathRubberBand = canvasItemList['path'] for row in rows: cur2 = con.cursor() @@ -76,11 +81,11 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): assert row2, "Invalid result geometry. (node_id:%(result_node_id)d, edge_id:%(result_edge_id)d)" % args geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: resultPathRubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): resultPathRubberBand.addPoint(pt) diff --git a/functions/trsp_via_edges.py b/functions/trsp_via_edges.py index 69b085d..f009d12 100755 --- a/functions/trsp_via_edges.py +++ b/functions/trsp_via_edges.py @@ -1,33 +1,30 @@ from __future__ import absolute_import -#from PyQt4.QtCore import * +from qgis.PyQt.QtCore import * from builtins import str from qgis.PyQt.QtGui import QColor -from qgis.core import QGis, QgsGeometry +from qgis.core import Qgis, QgsGeometry, QgsWkbTypes from qgis.gui import QgsRubberBand import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): - + @classmethod def getName(self): + ''' returns Function name. ''' return 'trsp(via edges)' - + @classmethod def getControlNames(self, version): - return [ - 'labelId', 'lineEditId', - 'labelSource', 'lineEditSource', - 'labelTarget', 'lineEditTarget', - 'labelCost', 'lineEditCost', - 'labelReverseCost', 'lineEditReverseCost', + ''' returns control names. ''' + return self.commonControls + self.commonBoxes + [ 'labelIds', 'lineEditIds', 'buttonSelectIds', 'labelPcts', 'lineEditPcts', 'checkBoxDirected', 'checkBoxHasReverseCost', 'labelTurnRestrictSql', 'plainTextEditTurnRestrictSql' ] - + @classmethod def isEdgeBase(self): return True @@ -39,9 +36,9 @@ def canExport(self): @classmethod def canExportMerged(self): return True - + def isSupportedVersion(self, version): - return version >= 2.1 and version < 3.0 + return version >= 2.1 def prepare(self, canvasItemList): resultPathsRubberBands = canvasItemList['paths'] @@ -50,16 +47,18 @@ def prepare(self, canvasItemList): canvasItemList['paths'] = [] def getQuery(self, args): + ''' returns the sql query in required signature format of trsp_via_edges ''' + args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) return """ SELECT seq, id1 AS _path, id2 AS _node, id3 AS _edge, cost as _cost FROM pgr_trspViaEdges(' 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 %(edge_table)s.%(geometry)s && %(BBOX)s', + %(where_clause)s', ARRAY[%(ids)s]::integer[], ARRAY[%(pcts)s]::float8[], %(directed)s, %(has_reverse_cost)s, %(turn_restrict_sql)s)""" % args - + def getQueries(self, args): args['edge_data_q'] = """ edge_data AS ( @@ -74,7 +73,7 @@ def getQueries(self, args): ST_Reverse(ST_LineSubstring(%(geometry)s, 0, fraction)) as tosource FROM %(edge_table)s JOIN edge_data ON (%(edge_table)s.%(id)s = edge_data.eid) ) - """ % args + """ % args args['result_q'] = """ result AS ( @@ -137,13 +136,13 @@ def getQueries(self, args): def getExportQuery(self, args): self.getQueries(args) - + query = """ WITH %(edge_data_q)s, %(result_q)s, %(the_rest_q)s - SELECT + SELECT all_edges.*, %(edge_table)s.* FROM %(edge_table)s JOIN all_edges ON %(edge_table)s.%(id)s = all_edges._edge ORDER BY all_edges.seq @@ -152,7 +151,7 @@ def getExportQuery(self, args): def getExportMergeQuery(self, args): self.getQueries(args) - + query = """ WITH %(edge_data_q)s, @@ -164,8 +163,9 @@ def getExportMergeQuery(self, args): ST_makeLine(path_geom) AS path_geom from all_edges """ % args return query - + def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' resultPathsRubberBands = canvasItemList['paths'] rubberBand = None cur_path_id = -1 @@ -249,22 +249,22 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): SELECT ST_AsText(%(transform_s)sST_Reverse(%(geometry)s)%(transform_e)s) FROM %(edge_table)s WHERE %(target)s = %(result_node_id)d AND %(id)s = %(result_edge_id)d; """ % args - + ##Utils.logMessage(query2) cur2.execute(query2) row2 = cur2.fetchone() ##Utils.logMessage(str(row2[0])) assert row2, "Invalid result geometry. (node_id:%(result_node_id)d, edge_id:%(result_edge_id)d)" % args - + geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: rubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): rubberBand.addPoint(pt) - + i = i + 1 if rubberBand: diff --git a/functions/trsp_via_vertices.py b/functions/trsp_via_vertices.py index ef317a9..e7778c2 100755 --- a/functions/trsp_via_vertices.py +++ b/functions/trsp_via_vertices.py @@ -1,37 +1,41 @@ from __future__ import absolute_import -#from PyQt4.QtCore import * +from qgis.PyQt.QtCore import * from builtins import str from qgis.PyQt.QtGui import QColor -from qgis.core import QGis, QgsGeometry +from qgis.core import Qgis, QgsGeometry, QgsWkbTypes from qgis.gui import QgsRubberBand import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): - + @classmethod def getName(self): + ''' returns Function name. ''' return 'trsp(via vertices)' - + @classmethod def getControlNames(self, version): + ''' returns control names. ''' return self.commonControls + self.commonBoxes + [ 'labelIds', 'lineEditIds', 'buttonSelectIds', 'labelTurnRestrictSql', 'plainTextEditTurnRestrictSql' ] - + def isSupportedVersion(self, version): - return version >= 2.1 and version < 3.0 + ''' checks the supported version ''' + return version >= 2.1 def prepare(self, canvasItemList): resultPathsRubberBands = canvasItemList['paths'] for path in resultPathsRubberBands: path.reset(Utils.getRubberBandType(False)) canvasItemList['paths'] = [] - + def getQuery(self, args): + ''' returns the sql query in required signature format of trsp_via_vertices ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) return """ SELECT seq, id1 AS _path, id2 AS _node, id3 AS _edge, cost AS _cost FROM pgr_trspViaVertices(' @@ -44,14 +48,14 @@ def getQuery(self, args): %(directed)s, %(has_reverse_cost)s, %(turn_restrict_sql)s) """ % args - + def getExportQuery(self, args): args['result_query'] = self.getQuery(args) query = """ WITH result AS ( %(result_query)s ) - SELECT + SELECT CASE WHEN result._node = %(edge_table)s.%(source)s THEN %(edge_table)s.%(geometry)s @@ -103,13 +107,13 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): row2 = cur2.fetchone() ##Utils.logMessage(str(row2[0])) assert row2, "Invalid result geometry. (node_id:%(result_node_id)d, edge_id:%(result_edge_id)d)" % args - + geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: rubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): rubberBand.addPoint(pt) diff --git a/functions/tsp_euclid.py b/functions/tsp_euclid.py index 007fde1..7693033 100755 --- a/functions/tsp_euclid.py +++ b/functions/tsp_euclid.py @@ -1,19 +1,23 @@ -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * +from __future__ import absolute_import +from builtins import str +from qgis.PyQt.QtCore import QPointF, QSizeF +from qgis.PyQt.QtGui import QTextDocument +from qgis.core import QgsGeometry, Qgis, QgsTextAnnotation, QgsWkbTypes from qgis.gui import * import psycopg2 -from .. import pgRoutingLayer_utils as Utils -from FunctionBase import FunctionBase +from pgRoutingLayer import pgRoutingLayer_utils as Utils +from .FunctionBase import FunctionBase class Function(FunctionBase): - + @classmethod def getName(self): + ''' returns Function name. ''' return 'tsp(euclid)' - + @classmethod def getControlNames(self, version): + ''' returns control names. ''' return [ 'labelId', 'lineEditId', 'labelSource', 'lineEditSource', @@ -22,7 +26,7 @@ def getControlNames(self, version): 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', 'labelTargetId', 'lineEditTargetId', 'buttonSelectTargetId' ] - + @classmethod def canExport(self): return False @@ -32,15 +36,16 @@ def canExportMerged(self): return False def isSupportedVersion(self, version): - return version >= 2.0 and version < 3.0 + return version >= 2.0 def prepare(self, canvasItemList): resultNodesTextAnnotations = canvasItemList['annotations'] for anno in resultNodesTextAnnotations: anno.setVisible(False) canvasItemList['annotations'] = [] - + def getQuery(self, args): + ''' returns the sql query in required signature format of tsp_euclid ''' return """ SELECT seq, id1 AS internal, id2 AS node, cost FROM pgr_tsp(' SELECT id::int4, @@ -50,14 +55,14 @@ def getQuery(self, args): FROM %(edge_table)s_vertices_pgr WHERE id IN (%(ids)s)', %(source_id)s::int4, %(target_id)s::int4) """ % args - + def getExportQuery(self, args): args['result_query'] = self.getQuery(args) query = """ WITH result AS ( %(result_query)s ) - SELECT + SELECT CASE WHEN result._node = %(edge_table)s.%(source)s THEN %(edge_table)s.%(geometry)s @@ -70,20 +75,21 @@ def getExportQuery(self, args): return query def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' resultPathsRubberBands = canvasItemList['path'] i = 0 for row in rows: if i == 0: prevrow = row firstrow = row - i += 1 + i += 1 cur2 = con.cursor() args['result_seq'] = row[0] args['result_source_id'] = prevrow[2] args['result_target_id'] = row[2] args['result_cost'] = row[3] query2 = """ - SELECT ST_AsText( ST_MakeLine( + SELECT ST_AsText( ST_MakeLine( (SELECT the_geom FROM %(edge_table)s_vertices_pgr WHERE id = %(result_source_id)d), (SELECT the_geom FROM %(edge_table)s_vertices_pgr WHERE id = %(result_target_id)d) )) @@ -95,37 +101,37 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): assert row2, "Invalid result geometry. (path_id:%(result_path_id)d, saource_id:%(result_source_id)d, target_id:%(result_target_id)d)" % args geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: resultPathsRubberBands.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): resultPathsRubberBands.addPoint(pt) prevrow = row lastrow = row - + args['result_source_id'] = lastrow[2] args['result_target_id'] = firstrow[2] args['result_cost'] = row[3] query2 = """ - SELECT ST_AsText( ST_MakeLine( + SELECT ST_AsText( ST_MakeLine( (SELECT the_geom FROM %(edge_table)s_vertices_pgr WHERE id = %(result_source_id)d), (SELECT the_geom FROM %(edge_table)s_vertices_pgr WHERE id = %(result_target_id)d) )) """ % args - ##Utils.logMessage(query2) + # Utils.logMessage(query2) cur2.execute(query2) row2 = cur2.fetchone() - ##Utils.logMessage(str(row2[0])) + # Utils.logMessage(str(row2[0])) assert row2, "Invalid result geometry. (path_id:%(result_path_id)d, saource_id:%(result_source_id)d, target_id:%(result_target_id)d)" % args geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: resultPathsRubberBands.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): resultPathsRubberBands.addPoint(pt) @@ -154,17 +160,18 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): cur2.execute(query2) row2 = cur2.fetchone() assert row2, "Invalid result geometry. (node_id:%(result_node_id)d)" % args - + geom = QgsGeometry().fromWkt(str(row2[0])) pt = geom.asPoint() textDocument = QTextDocument("%(result_seq)d:%(result_node_id)d" % args) - textAnnotation = QgsTextAnnotationItem(mapCanvas) + textAnnotation = QgsTextAnnotation() textAnnotation.setMapPosition(geom.asPoint()) textAnnotation.setFrameSize(QSizeF(textDocument.idealWidth(), 20)) - textAnnotation.setOffsetFromReferencePoint(QPointF(20, -40)) + textAnnotation.setFrameOffsetFromReferencePoint(QPointF(20, -40)) textAnnotation.setDocument(textDocument) - textAnnotation.update() + + QgsMapCanvasAnnotationItem(textAnnotation, mapCanvas) resultNodesTextAnnotations.append(textAnnotation) - + def __init__(self, ui): FunctionBase.__init__(self, ui) diff --git a/functions/with_Points.py b/functions/with_Points.py new file mode 100644 index 0000000..fd6ef53 --- /dev/null +++ b/functions/with_Points.py @@ -0,0 +1,110 @@ +from __future__ import absolute_import +from builtins import str +from qgis.PyQt.QtCore import QSizeF, QPointF +from qgis.PyQt.QtGui import QColor, QTextDocument +from qgis.core import QgsGeometry, Qgis, QgsTextAnnotation, QgsWkbTypes, QgsAnnotation +from qgis.gui import QgsRubberBand +import psycopg2 +from pgRoutingLayer import pgRoutingLayer_utils as Utils +from .FunctionBase import FunctionBase + +class Function(FunctionBase): + + @classmethod + def getName(self): + ''' returns Function name. ''' + return 'with_Points' + + @classmethod + def getControlNames(self, version): + ''' returns control names. ''' + self.version = version + if self.version < 2.1: + # version 2.0 has only one to one + return self.commonControls + self.commonBoxes + [ + 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', + 'labelTargetId','labelSide','lineEditSide','lineEditEdge_id','labelFraction','lineEditFraction', 'lineEditPointsTable', 'lineEditTargetId','labelPid','lineEditPid','labelEdge_id', 'buttonSelectTargetId', + 'labelEdge_id','labelSide' + + ] + else: + return self.commonControls + self.commonBoxes + [ + 'labelSourceIds', 'lineEditSourceIds', 'buttonSelectSourceIds', + 'labelTargetIds', 'lineEditPointsTable', 'label_pointsTable', 'lineEditTargetIds', 'buttonSelectTargetIds', + 'labelSide','lineEditSide','lineEditEdge_id','labelEdge_id','labelFraction','lineEditFraction','labelPid','lineEditPid', + 'labelDrivingSide','checkBoxLeft','checkBoxRight','checkBoxDetails' + ] + + + @classmethod + def isSupportedVersion(self, version): + ''' Checks supported version ''' + # valid starting pgr v2.1 + return version >= 2.1 + + + @classmethod + def canExportQuery(self): + return False + + @classmethod + def canExportMerged(self): + return False + + + + def prepare(self, canvasItemList): + resultNodesTextAnnotations = canvasItemList['annotations'] + for anno in resultNodesTextAnnotations: + anno.setVisible(False) + canvasItemList['annotations'] = [] + + + def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_withPoints ''' + args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) + args['where_clause_with'] = self.whereClause(args['points_table'], args['geometry'], args['BBOX']) + return """ + SELECT seq, path_seq AS path_seq, + start_vid AS _start_vid , end_vid AS _end_vid, node, cost AS _cost, lead(agg_cost) over() AS agg_cost + FROM pgr_withPoints(' + 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 + ', + 'SELECT %(pid)s AS pid, + %(edge_id)s AS edge_id, + %(fraction)s AS fraction, + %(side)s AS side + FROM %(points_table)s + %(where_clause_with)s', + array[%(source_ids)s]::BIGINT[], array[%(target_ids)s]::BIGINT[], %(directed)s, '%(driving_side)s', %(details)s) + """ % args + + def getExportQuery(self, args): + return self.getJoinResultWithEdgeTable(args) + + + def getExportMergeQuery(self, args): + if self.version < 2.1: + return self.getExportOneSourceOneTargetMergeQuery(args) + else: + return self.getExportManySourceManyTargetMergeQuery(args) + + + + def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' + if self.version < 2.1: + self.drawOnePath(rows, con, args, geomType, canvasItemList, mapCanvas) + else: + self.drawManyPaths(rows, con, args, geomType, canvasItemList, mapCanvas) + + + + def __init__(self, ui): + FunctionBase.__init__(self, ui) diff --git a/functions/kdijkstra_cost.py b/functions/with_PointsCost.py old mode 100755 new mode 100644 similarity index 59% rename from functions/kdijkstra_cost.py rename to functions/with_PointsCost.py index 5ba6764..f306a82 --- a/functions/kdijkstra_cost.py +++ b/functions/with_PointsCost.py @@ -1,63 +1,92 @@ from __future__ import absolute_import from builtins import str from qgis.PyQt.QtCore import QSizeF, QPointF -from qgis.PyQt.QtGui import QColor -from qgis.core import QGis, QgsGeometry -from qgis.gui import QgsRubberBand, QgsTextAnnotationItem +from qgis.PyQt.QtGui import QColor, QTextDocument +from qgis.core import QgsGeometry, Qgis, QgsTextAnnotation, QgsWkbTypes, QgsAnnotation +from qgis.gui import QgsRubberBand import psycopg2 -from .. import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils from .FunctionBase import FunctionBase class Function(FunctionBase): - + @classmethod def getName(self): - return 'kdijkstra(cost)' + ''' returns Function name. ''' + return 'with_PointsCost' @classmethod - def isSupportedVersion(self, version): - # Deprecated on version 2.2 - return version >= 2.0 and version < 2.2 + def getControlNames(self, version): + ''' returns control names. ''' + self.version = version + if self.version < 2.1: + # version 2.0 has only one to one + return self.commonControls + self.commonBoxes + [ + 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', + 'labelTargetId','labelSide','lineEditSide','lineEditEdge_id','labelFraction','lineEditFraction', 'lineEditPointsTable', 'lineEditTargetId','labelPid','lineEditPid','labelEdge_id', 'buttonSelectTargetId', + 'labelEdge_id','labelSide' + + ] + else: + return self.commonControls + self.commonBoxes + [ + 'labelSourceIds', 'lineEditSourceIds', 'buttonSelectSourceIds', + 'labelTargetIds', 'lineEditPointsTable', 'label_pointsTable', 'lineEditTargetIds', 'buttonSelectTargetIds', + 'labelSide','lineEditSide','lineEditEdge_id','labelEdge_id','labelFraction','lineEditFraction','labelPid','lineEditPid', + 'labelDrivingSide','checkBoxLeft','checkBoxRight' + ] + @classmethod - def getControlNames(self, version): - # version 2.0 has only one to many - return self.commonControls + self.commonBoxes + [ - 'labelSourceId', 'lineEditSourceId', 'buttonSelectSourceId', - 'labelTargetIds', 'lineEditTargetIds', 'buttonSelectTargetIds', - ] + def isSupportedVersion(self, version): + ''' Checks supported version ''' + # valid starting pgr v2.1 + return version >= 2.1 + - @classmethod - def canExport(self): - return True - + def canExportQuery(self): + return False + @classmethod def canExportMerged(self): return False - + + + def prepare(self, canvasItemList): resultNodesTextAnnotations = canvasItemList['annotations'] for anno in resultNodesTextAnnotations: anno.setVisible(False) - self.iface.mapCanvas().scene().removeItem(anno) canvasItemList['annotations'] = [] - + + def getQuery(self, args): + ''' returns the sql query in required signature format of pgr_withPointsCost ''' args['where_clause'] = self.whereClause(args['edge_table'], args['geometry'], args['BBOX']) + args['where_clause_with'] = self.whereClause(args['points_table'], args['geometry'], args['BBOX']) return """ - SELECT seq, id1 AS source, id2 AS target, cost FROM pgr_kdijkstraCost(' - 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, array[%(target_ids)s], %(directed)s, %(has_reverse_cost)s)""" % args - + SELECT start_vid AS start_vid , end_vid AS end_vid, cost AS cost + FROM pgr_withPointsCost(' + 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 + ', + 'SELECT %(pid)s AS pid, + %(edge_id)s AS edge_id, + %(fraction)s AS fraction, + %(side)s AS side + FROM %(points_table)s + %(where_clause_with)s', + array[%(source_ids)s]::BIGINT[], array[%(target_ids)s]::BIGINT[], %(directed)s, '%(driving_side)s') + """ % args + def getExportQuery(self, args): args['result_query'] = self.getQuery(args) - args['vertex_table'] = """ + args['vertex_table'] = """ %(edge_table)s_vertices_pgr """ % args @@ -67,12 +96,15 @@ def getExportQuery(self, args): SELECT result.*, ST_MakeLine(a.the_geom, b.the_geom) AS path_geom FROM result - JOIN %(vertex_table)s AS a ON (source = a.id) - JOIN %(vertex_table)s AS b ON (target = b.id) + JOIN %(vertex_table)s AS a ON (start_vid = a.id) + JOIN %(vertex_table)s AS b ON (end_vid = b.id) """ % args + + def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): + ''' draw the result ''' resultPathsRubberBands = canvasItemList['paths'] rubberBand = None cur_path_id = -1 @@ -93,7 +125,7 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): rubberBand.setWidth(4) if args['result_cost'] != -1: query2 = """ - SELECT ST_AsText( ST_MakeLine( + SELECT ST_AsText( ST_MakeLine( (SELECT the_geom FROM %(edge_table)s_vertices_pgr WHERE id = %(result_source_id)d), (SELECT the_geom FROM %(edge_table)s_vertices_pgr WHERE id = %(result_target_id)d) )) @@ -105,19 +137,17 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): assert row2, "Invalid result geometry. (path_id:%(result_path_id)d, saource_id:%(result_source_id)d, target_id:%(result_target_id)d)" % args geom = QgsGeometry().fromWkt(str(row2[0])) - if geom.wkbType() == QGis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: rubberBand.addPoint(pt) - elif geom.wkbType() == QGis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): rubberBand.addPoint(pt) if rubberBand: resultPathsRubberBands.append(rubberBand) rubberBand = None - - resultNodesTextAnnotations = canvasItemList['annotations'] Utils.setStartPoint(geomType, args) Utils.setEndPoint(geomType, args) @@ -137,20 +167,20 @@ def draw(self, rows, con, args, geomType, canvasItemList, mapCanvas): cur2.execute(query2) row2 = cur2.fetchone() assert row2, "Invalid result geometry. (target_id:%(result_target_id)d)" % args - + geom = QgsGeometry().fromWkt(str(row2[0])) pt = geom.asPoint() textDocument = QTextDocument("%(result_target_id)d:%(result_cost)f" % args) - textAnnotation = QgsTextAnnotationItem(mapCanvas) + textAnnotation = QgsTextAnnotation() textAnnotation.setMapPosition(geom.asPoint()) textAnnotation.setFrameSize(QSizeF(textDocument.idealWidth(), 20)) - textAnnotation.setOffsetFromReferencePoint(QPointF(20, -40)) + textAnnotation.setFrameOffsetFromReferencePoint(QPointF(20, -40)) textAnnotation.setDocument(textDocument) - - textAnnotation.update() + + QgsMapCanvasAnnotationItem(textAnnotation, mapCanvas) resultNodesTextAnnotations.append(textAnnotation) - canvasItemList['annotations'] = resultNodesTextAnnotations - + + def __init__(self, ui): FunctionBase.__init__(self, ui) diff --git a/icon.png b/icon.png old mode 100644 new mode 100755 diff --git a/icons/add.png b/icons/add.png old mode 100644 new mode 100755 diff --git a/metadata.txt b/metadata.txt old mode 100644 new mode 100755 index 638dbf8..4832c8d --- a/metadata.txt +++ b/metadata.txt @@ -4,7 +4,7 @@ description=Dockable widget that adds pgRouting layers about=Dockable widget that adds pgRouting layers version=2.2.2 qgisMinimumVersion=3.0 -qgisMaximumVersion=3.1 +qgisMaximumVersion=4.0 author=Anita Graser, Ko Nagase, Vicky Vergara email=project@pgrouting.org changelog=2.2.2 diff --git a/pgRoutingLayer.py b/pgRoutingLayer.py index 87faf41..e82e8dd 100755 --- a/pgRoutingLayer.py +++ b/pgRoutingLayer.py @@ -2,8 +2,8 @@ /*************************************************************************** pgRouting Layer a QGIS plugin - - based on "Fast SQL Layer" plugin. Copyright 2011 Pablo Torres Carreira + + based on "Fast SQL Layer" plugin. Copyright 2011 Pablo Torres Carreira ------------------- begin : 2011-11-25 copyright : (c) 2011 by Anita Graser @@ -25,14 +25,13 @@ from builtins import object from qgis.PyQt import uic from qgis.PyQt.QtCore import Qt, QObject, pyqtSignal, QRegExp, QSettings - -from qgis.PyQt.QtGui import QColor, QIcon, QIntValidator, QDoubleValidator -from qgis.PyQt.QtWidgets import QAction -from qgis.core import QgsMessageLog,Qgis +from qgis.PyQt.QtGui import QColor, QIcon, QIntValidator, QDoubleValidator,QRegExpValidator, QCursor +from qgis.PyQt.QtWidgets import QAction, QDockWidget, QApplication, QLabel, QLineEdit, QPushButton, QWidget,QGridLayout,QToolButton,QVBoxLayout,QHBoxLayout,QSplitter,QGroupBox,QScrollArea,QPlainTextEdit, QMessageBox +from qgis.core import QgsMessageLog,Qgis,QgsRectangle, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject, QgsGeometry,QgsWkbTypes from qgis.gui import QgsVertexMarker,QgsRubberBand,QgsMapToolEmitPoint -from . import dbConnection +from pgRoutingLayer import dbConnection from qgis.utils import iface -from . import pgRoutingLayer_utils as Utils +from pgRoutingLayer import pgRoutingLayer_utils as Utils #import highlighter as hl import os import psycopg2 @@ -44,20 +43,20 @@ class PgRoutingLayer(object): SUPPORTED_FUNCTIONS = [ 'dijkstra', - 'dijkstraCost', 'astar', - 'drivingDistance', - 'alphashape', - 'tsp_euclid', - 'trsp_vertex', - 'trsp_edge', - 'kdijkstra_cost', - 'kdijkstra_path', 'bdDijkstra', 'bdAstar', 'ksp', + 'trsp_vertex', + 'trsp_edge', 'trsp_via_vertices', - 'trsp_via_edges' + 'trsp_via_edges', + 'drivingDistance', + 'alphashape', + # 'dijkstraCost', + # 'tsp_euclid', + # 'with_Points', + # 'with_PointsCost' ] TOGGLE_CONTROL_NAMES = [ @@ -88,16 +87,21 @@ class PgRoutingLayer(object): 'checkBoxHeapPaths', 'checkBoxUseBBOX', 'labelTurnRestrictSql', 'plainTextEditTurnRestrictSql', + # 'checkBoxDetails', + # 'label_pointsTable','lineEditPointsTable', + # 'labelPid', 'lineEditPid', 'labelEdge_id', 'lineEditEdge_id', + # 'labelFraction', 'lineEditFraction', 'labelSide', 'lineEditSide','labelDrivingSide','checkBoxLeft','checkBoxRight' ] FIND_RADIUS = 10 FRACTION_DECIMAL_PLACES = 2 - version = 2.0 + version = 2.6 functions = {} - + + def __init__(self, iface): # Save reference to the QGIS interface self.iface = iface - + self.idsVertexMarkers = [] self.targetIdsVertexMarkers = [] self.sourceIdsVertexMarkers = [] @@ -116,7 +120,7 @@ def __init__(self, iface): self.targetIdRubberBand = QgsRubberBand(self.iface.mapCanvas(), Utils.getRubberBandType(False)) self.targetIdRubberBand.setColor(Qt.yellow) self.targetIdRubberBand.setWidth(4) - + self.canvasItemList = {} self.canvasItemList['markers'] = [] self.canvasItemList['annotations'] = [] @@ -131,19 +135,19 @@ def __init__(self, iface): if not Utils.isQGISv1(): resultAreaRubberBand.setBrushStyle(Qt.Dense4Pattern) self.canvasItemList['area'] = resultAreaRubberBand - + def initGui(self): # Create action that will start plugin configuration self.action = QAction(QIcon(":/plugins/pgRoutingLayer/icon.png"), "pgRouting Layer", self.iface.mainWindow()) #Add toolbar button and menu item self.iface.addPluginToDatabaseMenu("&pgRouting Layer", self.action) #self.iface.addToolBarIcon(self.action) - + #load the form path = os.path.dirname(os.path.abspath(__file__)) self.dock = uic.loadUi(os.path.join(path, "ui_pgRoutingLayer.ui")) self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dock) - + self.idsEmitPoint = QgsMapToolEmitPoint(self.iface.mapCanvas()) self.sourceIdEmitPoint = QgsMapToolEmitPoint(self.iface.mapCanvas()) self.targetIdEmitPoint = QgsMapToolEmitPoint(self.iface.mapCanvas()) @@ -154,7 +158,7 @@ def initGui(self): #self.targetIdEmitPoint.setButton(buttonSelectTargetId) #self.sourceIdEmitPoint.setButton(buttonSelectSourceId) #self.targetIdsEmitPoint.setButton(buttonSelectTargetId) - + #connect the action to each method self.action.triggered.connect(self.show) self.dock.buttonReloadConnections.clicked.connect(self.reloadConnections) @@ -189,11 +193,11 @@ def initGui(self): self.functions = {} for funcfname in self.SUPPORTED_FUNCTIONS: # import the function - exec("from functions import %s as function" % funcfname) + exec("from pgRoutingLayer.functions import %s as function" % funcfname, globals(),globals()) funcname = function.Function.getName() self.functions[funcname] = function.Function(self.dock) self.dock.comboBoxFunction.addItem(funcname) - + self.dock.lineEditIds.setValidator(QRegExpValidator(QRegExp("[0-9,]+"), self.dock)) self.dock.lineEditPcts.setValidator(QRegExpValidator(QRegExp("[0-9,.]+"), self.dock)) @@ -209,25 +213,27 @@ def initGui(self): self.dock.lineEditDistance.setValidator(QDoubleValidator()) self.dock.lineEditAlpha.setValidator(QDoubleValidator()) self.dock.lineEditPaths.setValidator(QIntValidator()) - + #populate the combo with connections self.reloadMessage = False self.reloadConnections() self.loadSettings() Utils.logMessage("startup version " + str(self.version)) self.reloadMessage = True - + def show(self): self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dock) - + def unload(self): + ''' Removes the plugin menu item and icon''' self.clear() self.saveSettings() # Remove the plugin menu item and icon self.iface.removePluginDatabaseMenu("&pgRouting Layer", self.action) self.iface.removeDockWidget(self.dock) - + def reloadConnections(self): + ''' Reloads the connection with database. ''' oldReloadMessage = self.reloadMessage self.reloadMessage = False database = str(self.dock.comboConnections.currentText()) @@ -256,7 +262,7 @@ def reloadConnections(self): db.con.close() idx = self.dock.comboConnections.findText(database) - + if idx >= 0: self.dock.comboConnections.setCurrentIndex(idx) else: @@ -267,6 +273,7 @@ def reloadConnections(self): def updateConnectionEnabled(self): + ''' Updates the database connection name and function ''' dbname = str(self.dock.comboConnections.currentText()) if dbname =='': return @@ -275,23 +282,24 @@ def updateConnectionEnabled(self): con = db.con self.version = Utils.getPgrVersion(con) if self.reloadMessage: - QMessageBox.information(self.dock, self.dock.windowTitle(), + QMessageBox.information(self.dock, self.dock.windowTitle(), 'Selected database: ' + dbname + '\npgRouting version: ' + str(self.version)) - currentFunction = self.dock.comboBoxFunction.currentText() - if currentFunction =='': + currentFunction = str (self.dock.comboBoxFunction.currentText()) + if currentFunction ==' ': return self.loadFunctionsForVersion() self.updateFunctionEnabled(currentFunction) def loadFunctionsForVersion(self): + ''' Loads function names based on pgr version. ''' currentText = str(self.dock.comboBoxFunction.currentText()) self.dock.comboBoxFunction.clear() #for funcname, function in self.functions.items(): - for funcname in sorted(self.functions): + for funcname in self.functions: function = self.functions[funcname] if (function.isSupportedVersion(self.version)): self.dock.comboBoxFunction.addItem(function.getName()) @@ -303,21 +311,23 @@ def loadFunctionsForVersion(self): def updateFunctionEnabled(self, text): - if text == '': + ''' Updates the GUI fields of the selected function. ''' + text = str (self.dock.comboBoxFunction.currentText()) + if text== '': return self.clear() - function = self.functions[str(text)] - + function = self.functions.get(str(text)) + self.toggleSelectButton(None) - + for controlName in self.TOGGLE_CONTROL_NAMES: control = getattr(self.dock, controlName) control.setVisible(False) - + for controlName in function.getControlNames(self.version): control = getattr(self.dock, controlName) control.setVisible(True) - + # for initial display self.dock.gridLayoutSqlColumns.invalidate() self.dock.gridLayoutArguments.invalidate() @@ -328,19 +338,20 @@ def updateFunctionEnabled(self, text): if (not self.dock.checkBoxHasReverseCost.isChecked()) or (not self.dock.checkBoxHasReverseCost.isEnabled()): self.dock.lineEditReverseCost.setEnabled(False) - + # if type(edge/node) changed, clear input if (self.prevType != None) and (self.prevType != function.isEdgeBase()): self.clear() - + self.prevType = function.isEdgeBase() canExport = function.canExport() self.dock.buttonExport.setEnabled(canExport) canExportMerged = function.canExportMerged() self.dock.buttonExportMerged.setEnabled(canExportMerged) - + def selectIds(self, checked): + ''' Selects the ids and dispaly on lineEdit. ''' if checked: self.toggleSelectButton(self.dock.buttonSelectIds) self.dock.lineEditIds.setText("") @@ -356,8 +367,9 @@ def selectIds(self, checked): self.iface.mapCanvas().setMapTool(self.idsEmitPoint) else: self.iface.mapCanvas().unsetMapTool(self.idsEmitPoint) - + def setIds(self, pt): + ''' Sets the ids on mapCanvas with color ''' function = self.functions[str(self.dock.comboBoxFunction.currentText())] args = self.getBaseArguments() mapCanvas = self.iface.mapCanvas() @@ -387,11 +399,11 @@ def setIds(self, pt): idRubberBand = QgsRubberBand(mapCanvas, Utils.getRubberBandType(False)) idRubberBand.setColor(Qt.yellow) idRubberBand.setWidth(4) - if geom.wkbType() == Qgis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: idRubberBand.addPoint(pt) - elif geom.wkbType() == Qgis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): idRubberBand.addPoint(pt) self.idsRubberBands.append(idRubberBand) @@ -407,8 +419,9 @@ def setIds(self, pt): vertexMarker.setCenter(pointGeom.asPoint()) self.idsVertexMarkers.append(vertexMarker) Utils.refreshMapCanvas(mapCanvas) - + def selectSourceId(self, checked): + ''' Selects the source id and dispaly its value on lineEdit. ''' if checked: self.toggleSelectButton(self.dock.buttonSelectSourceId) self.dock.lineEditSourceId.setText("") @@ -417,8 +430,9 @@ def selectSourceId(self, checked): self.iface.mapCanvas().setMapTool(self.sourceIdEmitPoint) else: self.iface.mapCanvas().unsetMapTool(self.sourceIdEmitPoint) - + def setSourceId(self, pt): + ''' Sets the source id by finding nearest node and displays in mapCanvas with color ''' function = self.functions[str(self.dock.comboBoxFunction.currentText())] args = self.getBaseArguments() if not function.isEdgeBase(): @@ -434,11 +448,11 @@ def setSourceId(self, pt): if result: self.dock.lineEditSourceId.setText(str(id)) geom = QgsGeometry().fromWkt(wkt) - if geom.wkbType() == Qgis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: self.sourceIdRubberBand.addPoint(pt) - elif geom.wkbType() == Qgis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): self.sourceIdRubberBand.addPoint(pt) self.dock.lineEditSourcePos.setText(str(pos)) @@ -447,9 +461,10 @@ def setSourceId(self, pt): self.sourceIdVertexMarker.setVisible(True) self.dock.buttonSelectSourceId.click() Utils.refreshMapCanvas(self.iface.mapCanvas()) - - + + def selectSourceIds(self, checked): + ''' Selects the source ids and dispaly its value on lineEdit. ''' if checked: self.toggleSelectButton(self.dock.buttonSelectSourceIds) self.dock.lineEditSourceIds.setText("") @@ -460,8 +475,9 @@ def selectSourceIds(self, checked): self.iface.mapCanvas().setMapTool(self.sourceIdsEmitPoint) else: self.iface.mapCanvas().unsetMapTool(self.sourceIdsEmitPoint) - + def setSourceIds(self, pt): + ''' Sets the source id by finding nearest node and displays in mapCanvas with color ''' args = self.getBaseArguments() result, id, wkt = self.findNearestNode(args, pt) if result: @@ -481,6 +497,7 @@ def setSourceIds(self, pt): def selectTargetId(self, checked): + ''' Selects the target id and dispaly its value on lineEdit. ''' if checked: self.toggleSelectButton(self.dock.buttonSelectTargetId) self.dock.lineEditTargetId.setText("") @@ -489,8 +506,9 @@ def selectTargetId(self, checked): self.iface.mapCanvas().setMapTool(self.targetIdEmitPoint) else: self.iface.mapCanvas().unsetMapTool(self.targetIdEmitPoint) - + def setTargetId(self, pt): + ''' Sets the target id by finding nearest node and displays in mapCanvas with color ''' function = self.functions[str(self.dock.comboBoxFunction.currentText())] args = self.getBaseArguments() if not function.isEdgeBase(): @@ -506,11 +524,11 @@ def setTargetId(self, pt): if result: self.dock.lineEditTargetId.setText(str(id)) geom = QgsGeometry().fromWkt(wkt) - if geom.wkbType() == Qgis.WKBMultiLineString: + if geom.wkbType() == QgsWkbTypes.MultiLineString: for line in geom.asMultiPolyline(): for pt in line: self.targetIdRubberBand.addPoint(pt) - elif geom.wkbType() == Qgis.WKBLineString: + elif geom.wkbType() == QgsWkbTypes.LineString: for pt in geom.asPolyline(): self.targetIdRubberBand.addPoint(pt) self.dock.lineEditTargetPos.setText(str(pos)) @@ -519,8 +537,9 @@ def setTargetId(self, pt): self.targetIdVertexMarker.setVisible(True) self.dock.buttonSelectTargetId.click() Utils.refreshMapCanvas(self.iface.mapCanvas()) - + def selectTargetIds(self, checked): + ''' Selects the target ids and dispaly its value on lineEdit. ''' if checked: self.toggleSelectButton(self.dock.buttonSelectTargetIds) self.dock.lineEditTargetIds.setText("") @@ -531,8 +550,9 @@ def selectTargetIds(self, checked): self.iface.mapCanvas().setMapTool(self.targetIdsEmitPoint) else: self.iface.mapCanvas().unsetMapTool(self.targetIdsEmitPoint) - + def setTargetIds(self, pt): + ''' Sets the target ids by finding nearest node and displays in mapCanvas with color ''' args = self.getBaseArguments() result, id, wkt = self.findNearestNode(args, pt) if result: @@ -549,74 +569,75 @@ def setTargetIds(self, pt): vertexMarker.setCenter(geom.asPoint()) self.targetIdsVertexMarkers.append(vertexMarker) Utils.refreshMapCanvas(mapCanvas) - + def updateReverseCostEnabled(self, state): + ''' Updates the reverse cost checkBox ''' if state == Qt.Checked: self.dock.lineEditReverseCost.setEnabled(True) else: self.dock.lineEditReverseCost.setEnabled(False) - + def run(self): """ Draws a Preview on the canvas""" QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) - + function = self.functions[str(self.dock.comboBoxFunction.currentText())] args = self.getArguments(function.getControlNames(self.version)) - + empties = [] for key in list(args.keys()): if not args[key]: empties.append(key) - + if len(empties) > 0: QApplication.restoreOverrideCursor() QMessageBox.warning(self.dock, self.dock.windowTitle(), 'Following argument is not specified.\n' + ','.join(empties)) return - + db = None try: dbname = str(self.dock.comboConnections.currentText()) db = self.actionsDb[dbname].connect() - + con = db.con - + version = Utils.getPgrVersion(con) args['version'] = version - + srid, geomType = Utils.getSridAndGeomType(con, args['edge_table'], args['geometry']) if (function.getName() == 'tsp(euclid)'): args['node_query'] = Utils.getNodeQuery(args, geomType) - + function.prepare(self.canvasItemList) - - args['BBOX'], args['printBBOX'] = self.getBBOX(srid, args['use_bbox']) + + args['BBOX'], args['printBBOX'] = self.getBBOX(srid, args['use_bbox']) query = function.getQuery(args) #QMessageBox.information(self.dock, self.dock.windowTitle(), 'Geometry Query:' + query) - + cur = con.cursor() cur.execute(query) rows = cur.fetchall() if len(rows) == 0: QMessageBox.information(self.dock, self.dock.windowTitle(), 'No paths found in ' + self.getLayerName(args)) - + args['srid'] = srid args['canvas_srid'] = Utils.getCanvasSrid(Utils.getDestinationCrs(self.iface.mapCanvas())) Utils.setTransformQuotes(args, srid, args['canvas_srid']) function.draw(rows, con, args, geomType, self.canvasItemList, self.iface.mapCanvas()) - + except psycopg2.DatabaseError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) - + except SystemError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) - + except AssertionError as e: QApplication.restoreOverrideCursor() QMessageBox.warning(self.dock, self.dock.windowTitle(), '%s' % e) - + finally: QApplication.restoreOverrideCursor() if db and db.con: @@ -627,29 +648,30 @@ def run(self): 'server closed the connection unexpectedly') def export(self): + ''' Exports the result layer ''' QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) - + function = self.functions[str(self.dock.comboBoxFunction.currentText())] args = self.getArguments(function.getControlNames(self.version)) - + empties = [] for key in list(args.keys()): if not args[key]: empties.append(key) - + if len(empties) > 0: QApplication.restoreOverrideCursor() QMessageBox.warning(self.dock, self.dock.windowTitle(), 'Following argument is not specified.\n' + ','.join(empties)) return - + db = None try: dbname = str(self.dock.comboConnections.currentText()) db = self.actionsDb[dbname].connect() - + con = db.con - + version = Utils.getPgrVersion(con) args['version'] = version @@ -659,18 +681,18 @@ def export(self): srid, geomType = Utils.getSridAndGeomType(con, '%(edge_table)s' % args, '%(geometry)s' % args) - args['BBOX'], args['printBBOX'] = self.getBBOX(srid, args['use_bbox']) + args['BBOX'], args['printBBOX'] = self.getBBOX(srid, args['use_bbox']) #get the EXPORT query msgQuery = function.getExportQuery(args) #QMessageBox.information(self.dock, self.dock.windowTitle(), 'Geometry Query:\n' + msgQuery) Utils.logMessage('Export:\n' + msgQuery) - + query = self.cleanQuery(msgQuery) - + uri = db.getURI() uri.setDataSource("", "(" + query + ")", "path_geom", "", "seq") - + layerName = self.getLayerName(args) vl = self.iface.addVectorLayer(uri.uri(), layerName, db.getProviderName()) @@ -678,15 +700,15 @@ def export(self): QMessageBox.information(self.dock, self.dock.windowTitle(), 'Invalid Layer:\n - No paths found or\n - Failed to create vector layer from query') #QMessageBox.information(self.dock, self.dock.windowTitle(), 'pgRouting Query:' + function.getQuery(args)) #QMessageBox.information(self.dock, self.dock.windowTitle(), 'Geometry Query:' + msgQuery) - + except psycopg2.DatabaseError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) - + except SystemError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) - + finally: QApplication.restoreOverrideCursor() if db and db.con: @@ -697,6 +719,7 @@ def export(self): 'server closed the connection unexpectedly') def cleanQuery(self, msgQuery): + ''' Cleans the query ''' query = msgQuery.replace('\n', ' ') query = re.sub(r'\s+', ' ', query) query = query.replace('( ', '(') @@ -731,50 +754,51 @@ def getBBOX(self, srid, use_bbox): %(xMax)s, %(yMax)s, %(srid)s )%(suffix)s """ % bbox, text - - + + def exportMerged(self): + ''' exports the result layer with input layer ''' QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) - + function = self.functions[str(self.dock.comboBoxFunction.currentText())] args = self.getArguments(function.getControlNames(self.version)) - + empties = [] for key in list(args.keys()): if not args[key]: empties.append(key) - + if len(empties) > 0: QApplication.restoreOverrideCursor() QMessageBox.warning(self.dock, self.dock.windowTitle(), 'Following argument is not specified.\n' + ','.join(empties)) return - + db = None try: dbname = str(self.dock.comboConnections.currentText()) db = self.actionsDb[dbname].connect() - + con = db.con - + version = Utils.getPgrVersion(con) args['version'] = version - + srid, geomType = Utils.getSridAndGeomType(con, '%(edge_table)s' % args, '%(geometry)s' % args) - args['BBOX'], args['printBBOX'] = self.getBBOX(srid, args['use_bbox']) + args['BBOX'], args['printBBOX'] = self.getBBOX(srid, args['use_bbox']) # get the exportMerge query msgQuery = function.getExportMergeQuery(args) Utils.logMessage('Export merged:\n' + msgQuery) query = self.cleanQuery(msgQuery) - + uri = db.getURI() uri.setDataSource("", "(" + query + ")", "path_geom", "", "seq") - + # add vector layer to map layerName = self.getLayerName(args, 'M') - + vl = self.iface.addVectorLayer(uri.uri(), layerName, db.getProviderName()) if not vl: @@ -788,15 +812,15 @@ def exportMerged(self): QMessageBox.information(self.dock, self.dock.windowTitle(), 'Invalid Layer:\n - No paths found') else: QMessageBox.information(self.dock, self.dock.windowTitle(), 'Invalid Layer:\n - No paths found or\n - Failed to create vector layer from query') - + except psycopg2.DatabaseError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) - + except SystemError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) - + finally: QApplication.restoreOverrideCursor() if db and db.con: @@ -805,11 +829,12 @@ def exportMerged(self): except: QMessageBox.critical(self.dock, self.dock.windowTitle(), 'server closed the connection unexpectedly') - + def getLayerName(self, args, letter=''): + ''' returns the layer Name ''' function = self.functions[str(self.dock.comboBoxFunction.currentText())] - layerName = "(" + letter + layerName = "(" + letter if 'directed' in args and args['directed'] == 'true': layerName += "D) " @@ -844,11 +869,12 @@ def getLayerName(self, args, letter=''): layerName += " " + args['printBBOX'] return layerName - + def clear(self): + ''' Clears the selected ids ''' #self.dock.lineEditIds.setText("") for marker in self.idsVertexMarkers: marker.setVisible(False) @@ -882,7 +908,6 @@ def clear(self): for anno in self.canvasItemList['annotations']: try: anno.setVisible(False) - self.iface.mapCanvas().scene().removeItem(anno) except RuntimeError as e: QApplication.restoreOverrideCursor() QMessageBox.critical(self.dock, self.dock.windowTitle(), '%s' % e) @@ -892,7 +917,7 @@ def clear(self): self.canvasItemList['paths'] = [] self.canvasItemList['path'].reset(Utils.getRubberBandType(False)) self.canvasItemList['area'].reset(Utils.getRubberBandType(True)) - + def toggleSelectButton(self, button): selectButtons = [ self.dock.buttonSelectIds, @@ -903,8 +928,9 @@ def toggleSelectButton(self, button): if selectButton != button: if selectButton.isChecked(): selectButton.click() - + def getArguments(self, controls): + ''' updates the GUI field text to args ''' args = {} args['edge_table'] = self.dock.lineEditTable.text() args['geometry'] = self.dock.lineEditGeometry.text() @@ -913,34 +939,50 @@ def getArguments(self, controls): if 'lineEditSource' in controls: args['source'] = self.dock.lineEditSource.text() - + if 'lineEditTarget' in controls: args['target'] = self.dock.lineEditTarget.text() - + + # if 'lineEditPointsTable' in controls: + # args['points_table'] = self.dock.lineEditPointsTable.text() + # + # if 'lineEditPid' in controls: + # args['pid'] = self.dock.lineEditPid.text() + # + # if 'lineEditEdge_id' in controls: + # args['edge_id'] = self.dock.lineEditEdge_id.text() + # + # if 'lineEditFraction' in controls: + # args['fraction'] = self.dock.lineEditFraction.text() + # + # if 'lineEditSide' in controls: + # args['side'] = self.dock.lineEditSide.text() + + if 'lineEditCost' in controls: args['cost'] = self.dock.lineEditCost.text() - + if 'lineEditReverseCost' in controls: args['reverse_cost'] = self.dock.lineEditReverseCost.text() - + if 'lineEditX1' in controls: args['x1'] = self.dock.lineEditX1.text() - + if 'lineEditY1' in controls: args['y1'] = self.dock.lineEditY1.text() - + if 'lineEditX2' in controls: args['x2'] = self.dock.lineEditX2.text() - + if 'lineEditY2' in controls: args['y2'] = self.dock.lineEditY2.text() - + if 'lineEditRule' in controls: args['rule'] = self.dock.lineEditRule.text() - + if 'lineEditToCost' in controls: args['to_cost'] = self.dock.lineEditToCost.text() - + if 'lineEditIds' in controls: args['ids'] = self.dock.lineEditIds.text() @@ -949,43 +991,51 @@ def getArguments(self, controls): if 'lineEditSourceId' in controls: args['source_id'] = self.dock.lineEditSourceId.text() - + if 'lineEditSourcePos' in controls: args['source_pos'] = self.dock.lineEditSourcePos.text() - + if 'lineEditSourceIds' in controls: args['source_ids'] = self.dock.lineEditSourceIds.text() - + if 'lineEditTargetId' in controls: args['target_id'] = self.dock.lineEditTargetId.text() - + if 'lineEditTargetPos' in controls: args['target_pos'] = self.dock.lineEditTargetPos.text() - + if 'lineEditTargetIds' in controls: args['target_ids'] = self.dock.lineEditTargetIds.text() - + if 'lineEditDistance' in controls: args['distance'] = self.dock.lineEditDistance.text() - + if 'lineEditAlpha' in controls: args['alpha'] = self.dock.lineEditAlpha.text() - + if 'lineEditPaths' in controls: args['paths'] = self.dock.lineEditPaths.text() - + if 'checkBoxDirected' in controls: args['directed'] = str(self.dock.checkBoxDirected.isChecked()).lower() - + + # if 'checkBoxDetails' in controls: + # args['details'] = str(self.dock.checkBoxDirected.isChecked()).lower() + if 'checkBoxHeapPaths' in controls: args['heap_paths'] = str(self.dock.checkBoxHeapPaths.isChecked()).lower() - + if 'checkBoxUseBBOX' in controls: args['use_bbox'] = str(self.dock.checkBoxUseBBOX.isChecked()).lower() else: args['use_bbox'] = 'false' - + # 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') if 'checkBoxHasReverseCost' in controls: args['has_reverse_cost'] = str(self.dock.checkBoxHasReverseCost.isChecked()).lower() @@ -993,36 +1043,38 @@ def getArguments(self, controls): args['reverse_cost'] = ' ' else: args['reverse_cost'] = ', ' + args['reverse_cost'] + '::float8 AS reverse_cost' - + if 'plainTextEditTurnRestrictSql' in controls: args['turn_restrict_sql'] = self.dock.plainTextEditTurnRestrictSql.toPlainText() - + return args - + def getBaseArguments(self): + ''' updates base arguments from GUI to args ''' args = {} args['edge_table'] = self.dock.lineEditTable.text() args['geometry'] = self.dock.lineEditGeometry.text() args['id'] = self.dock.lineEditId.text() args['source'] = self.dock.lineEditSource.text() args['target'] = self.dock.lineEditTarget.text() - + empties = [] for key in list(args.keys()): if not args[key]: empties.append(key) - + if len(empties) > 0: QApplication.restoreOverrideCursor() QMessageBox.warning(self.dock, self.dock.windowTitle(), 'Following argument is not specified.\n' + ','.join(empties)) return None - + return args - - + + # emulate "matching.sql" - "find_nearest_node_within_distance" def findNearestNode(self, args, pt): + ''' finds the nearest node to selected point ''' distance = self.iface.mapCanvas().getCoordinateTransform().mapUnitsPerPixel() * self.FIND_RADIUS rect = QgsRectangle(pt.x() - distance, pt.y() - distance, pt.x() + distance, pt.y() + distance) canvasCrs = Utils.getDestinationCrs(self.iface.mapCanvas()) @@ -1030,18 +1082,17 @@ def findNearestNode(self, args, pt): try: dbname = str(self.dock.comboConnections.currentText()) db = self.actionsDb[dbname].connect() - + con = db.con #srid, geomType = self.getSridAndGeomType(con, args) #srid, geomType = Utils.getSridAndGeomType(con, args['edge_table'], args['geometry']) srid, geomType = Utils.getSridAndGeomType(con, '%(edge_table)s' % args, '%(geometry)s' % args) - if self.iface.mapCanvas().hasCrsTransformEnabled(): - layerCrs = QgsCoordinateReferenceSystem() - Utils.createFromSrid(layerCrs, srid) - trans = QgsCoordinateTransform(canvasCrs, layerCrs) - pt = trans.transform(pt) - rect = trans.transform(rect) - + layerCrs = QgsCoordinateReferenceSystem() + Utils.createFromSrid(layerCrs, srid) + trans = QgsCoordinateTransform(canvasCrs, layerCrs, QgsProject.instance()) + pt = trans.transform(pt) + rect = trans.transform(rect) + args['canvas_srid'] = Utils.getCanvasSrid(canvasCrs) args['srid'] = srid args['x'] = pt.x() @@ -1050,12 +1101,12 @@ def findNearestNode(self, args, pt): args['miny'] = rect.yMinimum() args['maxx'] = rect.xMaximum() args['maxy'] = rect.yMaximum() - + Utils.setStartPoint(geomType, args) Utils.setEndPoint(geomType, args) #Utils.setTransformQuotes(args) Utils.setTransformQuotes(args, srid, args['canvas_srid']) - + # Getting nearest source query1 = """ SELECT %(source)s, @@ -1067,7 +1118,7 @@ def findNearestNode(self, args, pt): FROM %(edge_table)s WHERE ST_SetSRID('BOX3D(%(minx)f %(miny)f, %(maxx)f %(maxy)f)'::BOX3D, %(srid)d) && %(geometry)s ORDER BY dist ASC LIMIT 1""" % args - + ##Utils.logMessage(query1) cur1 = con.cursor() cur1.execute(query1) @@ -1079,7 +1130,7 @@ def findNearestNode(self, args, pt): d1 = row1[1] source = row1[0] wkt1 = row1[2] - + # Getting nearest target query2 = """ SELECT %(target)s, @@ -1091,7 +1142,7 @@ def findNearestNode(self, args, pt): FROM %(edge_table)s WHERE ST_SetSRID('BOX3D(%(minx)f %(miny)f, %(maxx)f %(maxy)f)'::BOX3D, %(srid)d) && %(geometry)s ORDER BY dist ASC LIMIT 1""" % args - + ##Utils.logMessage(query2) cur2 = con.cursor() cur2.execute(query2) @@ -1103,7 +1154,7 @@ def findNearestNode(self, args, pt): d2 = row2[1] target = row2[0] wkt2 = row2[2] - + # Checking what is nearer - source or target d = None node = None @@ -1125,26 +1176,27 @@ def findNearestNode(self, args, pt): node = target d = d2 wkt = wkt2 - + ##Utils.logMessage(str(d)) if (d == None) or (d > distance): node = None wkt = None return False, None, None - + return True, node, wkt - + 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: db.con.close() - + # emulate "matching.sql" - "find_nearest_link_within_distance" def findNearestLink(self, args, pt): + ''' finds the nearest link to selected point ''' distance = self.iface.mapCanvas().getCoordinateTransform().mapUnitsPerPixel() * self.FIND_RADIUS rect = QgsRectangle(pt.x() - distance, pt.y() - distance, pt.x() + distance, pt.y() + distance) canvasCrs = Utils.getDestinationCrs(self.iface.mapCanvas()) @@ -1152,19 +1204,19 @@ def findNearestLink(self, args, pt): try: dbname = str(self.dock.comboConnections.currentText()) db = self.actionsDb[dbname].connect() - + con = db.con cur = con.cursor() srid, geomType = Utils.getSridAndGeomType(con, '%(edge_table)s' % args, '%(geometry)s' % args) - if self.iface.mapCanvas().hasCrsTransformEnabled(): - layerCrs = QgsCoordinateReferenceSystem() - Utils.createFromSrid(layerCrs, srid) - trans = QgsCoordinateTransform(canvasCrs, layerCrs) - pt = trans.transform(pt) - rect = trans.transform(rect) - + + layerCrs = QgsCoordinateReferenceSystem() + Utils.createFromSrid(layerCrs, srid) + trans = QgsCoordinateTransform(canvasCrs, layerCrs, QgsProject.instance()) + pt = trans.transform(pt) + rect = trans.transform(rect) + args['canvas_srid'] = Utils.getCanvasSrid(canvasCrs) args['srid'] = srid args['x'] = pt.x() @@ -1174,10 +1226,10 @@ def findNearestLink(self, args, pt): args['maxx'] = rect.xMaximum() args['maxy'] = rect.yMaximum() args['decimal_places'] = self.FRACTION_DECIMAL_PLACES - + #Utils.setTransformQuotes(args) Utils.setTransformQuotes(args, srid, args['canvas_srid']) - + # Searching for a link within the distance query = """ WITH point AS ( @@ -1192,7 +1244,7 @@ def findNearestLink(self, args, pt): FROM %(edge_table)s, point WHERE ST_SetSRID('BOX3D(%(minx)f %(miny)f, %(maxx)f %(maxy)f)'::BOX3D, %(srid)d) && %(geometry)s ORDER BY dist ASC LIMIT 1""" % args - + ##Utils.logMessage(query) cur = con.cursor() cur.execute(query) @@ -1203,19 +1255,20 @@ def findNearestLink(self, args, pt): wkt = row[2] pos = row[3] pointWkt = row[4] - + return True, link, wkt, pos, pointWkt - + 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: db.con.close() - + def loadSettings(self): + ''' loads the default settings ''' settings = QSettings() idx = self.dock.comboConnections.findText(Utils.getStringValue(settings, '/pgRoutingLayer/Database', '')) if idx >= 0: @@ -1223,8 +1276,9 @@ def loadSettings(self): idx = self.dock.comboBoxFunction.findText(Utils.getStringValue(settings, '/pgRoutingLayer/Function', 'dijkstra')) if idx >= 0: self.dock.comboBoxFunction.setCurrentIndex(idx) - + self.dock.lineEditTable.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/edge_table', 'roads')) + # self.dock.lineEditPointsTable.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/pointsOfInterest', 'pointsOfInterest')) self.dock.lineEditGeometry.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/geometry', 'the_geom')) self.dock.lineEditId.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/id', 'id')) self.dock.lineEditSource.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/source', 'source')) @@ -1237,7 +1291,7 @@ def loadSettings(self): self.dock.lineEditY2.setText(Utils.getStringValue(settings, '/pgRoutingLayer/sql/y2', 'y2')) 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', '')) @@ -1257,13 +1311,14 @@ def loadSettings(self): self.dock.checkBoxHeapPaths.setChecked(Utils.getBoolValue(settings, '/pgRoutingLayer/heap_paths', False)) self.dock.checkBoxHasReverseCost.setChecked(Utils.getBoolValue(settings, '/pgRoutingLayer/has_reverse_cost', False)) self.dock.plainTextEditTurnRestrictSql.setPlainText(Utils.getStringValue(settings, '/pgRoutingLayer/turn_restrict_sql', 'null')) - + def saveSettings(self): settings = QSettings() settings.setValue('/pgRoutingLayer/Database', self.dock.comboConnections.currentText()) settings.setValue('/pgRoutingLayer/Function', self.dock.comboBoxFunction.currentText()) - + settings.setValue('/pgRoutingLayer/sql/edge_table', self.dock.lineEditTable.text()) + # settings.setValue('/pgRoutingLayer/sql/pointsOfInterest', self.dock.lineEditPointsTable.text()) settings.setValue('/pgRoutingLayer/sql/geometry', self.dock.lineEditGeometry.text()) settings.setValue('/pgRoutingLayer/sql/id', self.dock.lineEditId.text()) @@ -1279,7 +1334,7 @@ def saveSettings(self): settings.setValue('/pgRoutingLayer/sql/rule', self.dock.lineEditRule.text()) settings.setValue('/pgRoutingLayer/sql/to_cost', self.dock.lineEditToCost.text()) - + settings.setValue('/pgRoutingLayer/ids', self.dock.lineEditIds.text()) settings.setValue('/pgRoutingLayer/pcts', self.dock.lineEditPcts.text()) settings.setValue('/pgRoutingLayer/source_pos', self.dock.lineEditSourcePos.text()) @@ -1298,4 +1353,3 @@ def saveSettings(self): settings.setValue('/pgRoutingLayer/heap_paths', self.dock.checkBoxHeapPaths.isChecked()) settings.setValue('/pgRoutingLayer/has_reverse_cost', self.dock.checkBoxHasReverseCost.isChecked()) settings.setValue('/pgRoutingLayer/turn_restrict_sql', self.dock.plainTextEditTurnRestrictSql.toPlainText()) - diff --git a/pgRoutingLayer_utils.py b/pgRoutingLayer_utils.py old mode 100644 new mode 100755 index 6da6916..3af160d --- a/pgRoutingLayer_utils.py +++ b/pgRoutingLayer_utils.py @@ -1,4 +1,4 @@ -from qgis.core import QgsMessageLog,Qgis +from qgis.core import QgsMessageLog, Qgis, QgsWkbTypes from qgis.gui import QgsMapCanvas from qgis.PyQt.QtCore import QVariant, QSettings #from PyQt4.QtGui import * @@ -7,13 +7,15 @@ def getSridAndGeomType(con, table, geometry): + ''' retrieve Spatial Reference Id and geometry type, example 4326(WGS84) , Point ''' + args = {} args['table'] = table args['geometry'] = geometry cur = con.cursor() cur.execute(""" SELECT ST_SRID(%(geometry)s), ST_GeometryType(%(geometry)s) - FROM %(table)s + FROM %(table)s LIMIT 1 """ % args) row = cur.fetchone() @@ -21,18 +23,23 @@ def getSridAndGeomType(con, table, geometry): def setStartPoint(geomType, args): + ''' records startpoint of geometry and stores in args dictionary. ''' + if geomType == 'ST_MultiLineString': args['startpoint'] = "ST_StartPoint(ST_GeometryN(%(geometry)s, 1))" % args elif geomType == 'ST_LineString': args['startpoint'] = "ST_StartPoint(%(geometry)s)" % args def setEndPoint(geomType, args): + ''' records endpoint and stores in args. ''' + if geomType == 'ST_MultiLineString': args['endpoint'] = "ST_EndPoint(ST_GeometryN(%(geometry)s, 1))" % args elif geomType == 'ST_LineString': args['endpoint'] = "ST_EndPoint(%(geometry)s)" % args def setTransformQuotes(args, srid, canvas_srid): + ''' Sets transformQuotes ''' if srid > 0 and canvas_srid > 0: args['transform_s'] = "ST_Transform(" args['transform_e'] = ", %(canvas_srid)d)" % args @@ -41,55 +48,64 @@ def setTransformQuotes(args, srid, canvas_srid): args['transform_e'] = "" def isSIPv2(): + '''Checks the version of SIP ''' return sip.getapi('QVariant') > 1 def getStringValue(settings, key, value): + ''' returns key and its corresponding value. example: ("interval",30). ''' if isSIPv2(): return settings.value(key, value, type=str) else: return settings.value(key, QVariant(value)).toString() def getBoolValue(settings, key, value): + ''' returns True if settings exist otherwise False. ''' if isSIPv2(): return settings.value(key, value, type=bool) else: return settings.value(key, QVariant(value)).toBool() def isQGISv1(): - return QGis.QGIS_VERSION_INT < 10900 + ''' returns True if QGis has version l.9 or less, otherwise False. ''' + return Qgis.QGIS_VERSION_INT < 10900 def getDestinationCrs(mapCanvas): + ''' returns Coordinate Reference ID of map/overlaid layers. ''' if isQGISv1(): return mapCanvas.mapRenderer().destinationSrs() else: - if QGis.QGIS_VERSION_INT < 20400: + if Qgis.QGIS_VERSION_INT < 20400: return mapCanvas.mapRenderer().destinationCrs() else: return mapCanvas.mapSettings().destinationCrs() def getCanvasSrid(crs): + ''' Returns SRID based on QGIS version. ''' if isQGISv1(): return crs.epsg() else: return crs.postgisSrid() def createFromSrid(crs, srid): + ''' Creates EPSG crs for QGIS version 1 or Creates Spatial reference system based of SRID for QGIS version 2. ''' if isQGISv1(): return crs.createFromEpsg(srid) else: return crs.createFromSrid(srid) def getRubberBandType(isPolygon): + ''' returns RubberBandType as polygon or lineString ''' if isQGISv1(): return isPolygon else: if isPolygon: - return QGis.Polygon + return QgsWkbTypes.PolygonGeometry else: - return QGis.Line + return QgsWkbTypes.LineGeometry def refreshMapCanvas(mapCanvas): - if QGis.QGIS_VERSION_INT < 20400: + ''' refreshes the mapCanvas , RubberBand is cleared. ''' + if Qgis.QGIS_VERSION_INT < 20400: return mapCanvas.clear() else: return mapCanvas.refresh() @@ -98,6 +114,7 @@ def logMessage(message, level=Qgis.Info): QgsMessageLog.logMessage(message, 'pgRouting Layer', level) def getNodeQuery(args, geomType): + ''' returns a sql query to get nodes from a geometry. ''' setStartPoint(geomType, args) setEndPoint(geomType, args) return """ @@ -118,6 +135,7 @@ def getNodeQuery(args, geomType): )""" % args def getPgrVersion(con): + ''' returns version of PostgreSQL database. ''' try: cur = con.cursor() cur.execute('SELECT version FROM pgr_version()') @@ -129,7 +147,6 @@ def getPgrVersion(con): return float(version) except psycopg2.DatabaseError as e: #database didn't have pgrouting - return 0; + return 0 except SystemError as e: return 0 - diff --git a/readme.md b/readme.md old mode 100644 new mode 100755 index 6683b07..378244b --- a/readme.md +++ b/readme.md @@ -21,9 +21,8 @@ PgRoutingLayer currently supports the following functions: * bdAstar * bdDijkstra * dijkstra +* dijkstraCost * drivingDistance -* kdijkstra_cost -* kdijkstra_path * ksp * shootingStar * trsp_edge diff --git a/tests/Test_FunctionBase.py b/tests/Test_FunctionBase.py old mode 100644 new mode 100755 diff --git a/tests/Test_utils.py b/tests/Test_utils.py old mode 100644 new mode 100755 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/test_data/poly.PRJ b/tests/test_data/poly.PRJ old mode 100644 new mode 100755 diff --git a/tests/test_data/poly.dbf b/tests/test_data/poly.dbf old mode 100644 new mode 100755 diff --git a/tests/test_data/poly.shp b/tests/test_data/poly.shp old mode 100644 new mode 100755 diff --git a/tests/test_data/poly.shx b/tests/test_data/poly.shx old mode 100644 new mode 100755 diff --git a/tests/test_init.py b/tests/test_init.py old mode 100644 new mode 100755 diff --git a/ui_pgRoutingLayer.ui b/ui_pgRoutingLayer.ui old mode 100644 new mode 100755