Skip to content

Commit

Permalink
Enable to post external DB table with QML style
Browse files Browse the repository at this point in the history
  • Loading branch information
jirik committed Jan 26, 2023
1 parent ea8d095 commit 33e6f45
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 52 deletions.
38 changes: 24 additions & 14 deletions src/layman/layer/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,19 +204,26 @@ def get_text_column_names(schema, table_name, conn_cur=None):

def get_all_column_names(workspace, layername, conn_cur=None):
table_name = get_table_name(workspace, layername)
return [col.name for col in get_all_column_infos(workspace, table_name, conn_cur)]
return [col.name for col in get_all_column_infos(workspace, table_name, conn_cur=conn_cur)]


def get_all_column_infos(schema, table_name, conn_cur=None):
def get_all_column_infos(schema, table_name, *, conn_cur=None, omit_geometry_columns=False):
_, cur = conn_cur or db_util.get_connection_cursor()
query = """
SELECT inf.column_name, inf.data_type
FROM information_schema.columns inf
left outer join public.geometry_columns gc
on (inf.table_schema = gc.f_table_schema
and inf.table_name = gc.f_table_name
and inf.column_name = gc.f_geometry_column)
WHERE table_schema = %s
AND table_name = %s
"""
if omit_geometry_columns:
query += " AND gc.f_geometry_column is null"

try:
cur.execute(f"""
SELECT column_name AS column_name, data_type
FROM information_schema.columns
WHERE table_schema = '{schema}'
AND table_name = '{table_name}'
""")
cur.execute(query, (schema, table_name))
except BaseException as exc:
logger.error(f'get_all_column_names ERROR')
raise LaymanError(7) from exc
Expand Down Expand Up @@ -520,14 +527,17 @@ def get_crs(schema, table_name, conn_cur=None, column='wkb_geometry'):
return crs


def get_geometry_types(schema, table_name, conn_cur=None):
def get_geometry_types(schema, table_name, *, column_name='wkb_geometry', conn_cur=None):
conn, cur = conn_cur or db_util.get_connection_cursor()
query = sql.SQL("""
select distinct ST_GeometryType({column}) as geometry_type_name
from {table}
""").format(
table=sql.Identifier(schema, table_name),
column=sql.Identifier(column_name),
)
try:
sql = f"""
select distinct ST_GeometryType(wkb_geometry) as geometry_type_name
from {schema}.{table_name}
"""
cur.execute(sql)
cur.execute(query)
except BaseException as exc:
logger.error(f'get_geometry_types ERROR')
raise LaymanError(7) from exc
Expand Down
2 changes: 1 addition & 1 deletion src/layman/layer/qgis/layer-template.qml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</extent>
<id>{layer_name}_{layer_uuid}</id>
<datasource>dbname='{db_name}' host={db_host} port={db_port} user='{db_user}' password='{db_password}' sslmode=disable key='ogc_fid'
srid={srid} type={source_type} checkPrimaryKeyUnicity='1' table="{db_schema}"."{db_table}" (wkb_geometry)
srid={srid} type={source_type} checkPrimaryKeyUnicity='1' table="{db_schema}"."{db_table}" ({geo_column})
</datasource>
<keywordList>
<value></value>
Expand Down
2 changes: 1 addition & 1 deletion src/layman/layer/qgis/project-template.qgs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</projectCrs>
<layer-tree-group>
<customproperties/>
<layer-tree-layer source="dbname='{db_name}' host={db_host} port={db_port} user='{db_user}' password='{db_password}' sslmode=disable key='ogc_fid' srid={srid} type={source_type} checkPrimaryKeyUnicity='1' table=&quot;{db_schema}&quot;.&quot;{db_table}&quot; (wkb_geometry)" providerKey="postgres" legend_exp="" checked="Qt::Checked" id="{layer_name}_{layer_uuid}" expanded="1" name="{layer_name}">
<layer-tree-layer source="dbname='{db_name}' host={db_host} port={db_port} user='{db_user}' password='{db_password}' sslmode=disable key='ogc_fid' srid={srid} type={source_type} checkPrimaryKeyUnicity='1' table=&quot;{db_schema}&quot;.&quot;{db_table}&quot; ({geo_column})" providerKey="postgres" legend_exp="" checked="Qt::Checked" id="{layer_name}_{layer_uuid}" expanded="1" name="{layer_name}">
<customproperties/>
</layer-tree-layer>
<custom-order enabled="0">
Expand Down
36 changes: 21 additions & 15 deletions src/layman/layer/qgis/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import crs as crs_def
from db import util as db_util
from layman import settings, LaymanError
from layman import LaymanError
from layman.layer.filesystem import input_style
from layman.common import db as db_common
from . import wms
Expand Down Expand Up @@ -58,8 +58,10 @@ def get_layer_original_style_stream(workspace, layer):
return result


def fill_layer_template(workspace, layer, uuid, native_bbox, crs, qml_xml, source_type, attrs_to_ensure, table_name):
db_schema = workspace
def fill_layer_template(layer, uuid, native_bbox, crs, qml_xml, source_type, attrs_to_ensure, table_uri):
db_schema = table_uri.schema
table_name = table_uri.table
geo_column = table_uri.geo_column
layer_name = layer
wkb_type = source_type
qml_geometry = get_qml_geometry_from_qml(qml_xml)
Expand All @@ -68,14 +70,15 @@ def fill_layer_template(workspace, layer, uuid, native_bbox, crs, qml_xml, sourc
with open(template_path, 'r') as template_file:
template_str = template_file.read()
skeleton_xml_str = template_str.format(
db_name=settings.LAYMAN_PG_DBNAME,
db_host=settings.LAYMAN_PG_HOST,
db_port=settings.LAYMAN_PG_PORT,
db_user=settings.LAYMAN_PG_USER,
db_password=settings.LAYMAN_PG_PASSWORD,
db_name=table_uri.db_name,
db_host=table_uri.hostname,
db_port=table_uri.port,
db_user=table_uri.username,
db_password=table_uri.password,
source_type=source_type,
db_schema=db_schema,
db_table=table_name,
geo_column=geo_column,
layer_name=layer_name,
layer_uuid=uuid,
wkb_type=wkb_type,
Expand Down Expand Up @@ -114,24 +117,27 @@ def fill_layer_template(workspace, layer, uuid, native_bbox, crs, qml_xml, sourc
return full_xml_str


def fill_project_template(workspace, layer, layer_uuid, layer_qml, crs, epsg_codes, extent, source_type, table_name):
def fill_project_template(layer, layer_uuid, layer_qml, crs, epsg_codes, extent, source_type, table_uri):
wms_crs_list_values = "\n".join((f"<value>{code}</value>" for code in epsg_codes))
db_schema = workspace
db_schema = table_uri.table
table_name = table_uri.table
geo_column = table_uri.geo_column
layer_name = layer
creation_iso_datetime = datetime.datetime.utcnow().replace(microsecond=0).isoformat()

template_path = get_project_template_path()
with open(template_path, 'r') as template_file:
template_str = template_file.read()
return template_str.format(
db_name=settings.LAYMAN_PG_DBNAME,
db_host=settings.LAYMAN_PG_HOST,
db_port=settings.LAYMAN_PG_PORT,
db_user=settings.LAYMAN_PG_USER,
db_password=settings.LAYMAN_PG_PASSWORD,
db_name=table_uri.db_name,
db_host=table_uri.hostname,
db_port=table_uri.port,
db_user=table_uri.username,
db_password=table_uri.password,
source_type=source_type,
db_schema=db_schema,
db_table=table_name,
geo_column=geo_column,
layer_name=layer_name,
layer_uuid=layer_uuid,
layer_qml=layer_qml,
Expand Down
25 changes: 15 additions & 10 deletions src/layman/layer/qgis/wms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
from owslib.wms import WebMapService

import crs as crs_def
from db import util as db_util
from layman import patch_mode, settings, util as layman_util
from layman.common import bbox as bbox_util, empty_method, empty_method_returns_none, empty_method_returns_dict
from . import util
from .. import db, qgis, util as layer_util
from . import util, LAYER_TYPE
from .. import db, qgis

PATCH_MODE = patch_mode.DELETE_IF_DEPENDANT
VERSION = "1.1.1"
Expand Down Expand Up @@ -59,24 +60,28 @@ def get_layer_file_path(workspace, layer):


def save_qgs_file(workspace, layer):
info = layer_util.get_layer_info(workspace, layer)
info = layman_util.get_publication_info(workspace, LAYER_TYPE, layer, {'keys': ['uuid', 'native_bounding_box',
'table_uri']})
uuid = info['uuid']
qgis.ensure_layer_dir(workspace, layer)
layer_bbox = info['native_bounding_box']
crs = info['native_crs']
table_name = info['db_table']['name']
table_uri = info['_table_uri']
table_name = table_uri.table
db_schema = table_uri.schema
layer_bbox = layer_bbox if not bbox_util.is_empty(layer_bbox) else crs_def.CRSDefinitions[crs].default_bbox
qml = util.get_original_style_xml(workspace, layer)
qml_geometry = util.get_qml_geometry_from_qml(qml)
db_types = db.get_geometry_types(workspace, table_name)
conn_cur = db_util.create_connection_cursor(db_uri_str=table_uri.db_uri_str)
db_types = db.get_geometry_types(db_schema, table_name, conn_cur=conn_cur)
db_cols = [
col for col in db.get_all_column_infos(workspace, table_name)
if col.name not in ['wkb_geometry', 'ogc_fid']
col for col in db.get_all_column_infos(db_schema, table_name, conn_cur=conn_cur, omit_geometry_columns=True)
if col.name not in ['ogc_fid']
]
source_type = util.get_source_type(db_types, qml_geometry)
layer_qml = util.fill_layer_template(workspace, layer, uuid, layer_bbox, crs, qml, source_type, db_cols, table_name)
qgs_str = util.fill_project_template(workspace, layer, uuid, layer_qml, crs, settings.LAYMAN_OUTPUT_SRS_LIST,
layer_bbox, source_type, table_name)
layer_qml = util.fill_layer_template(layer, uuid, layer_bbox, crs, qml, source_type, db_cols, table_uri)
qgs_str = util.fill_project_template(layer, uuid, layer_qml, crs, settings.LAYMAN_OUTPUT_SRS_LIST,
layer_bbox, source_type, table_uri)
with open(get_layer_file_path(workspace, layer), "w") as qgs_file:
print(qgs_str, file=qgs_file)

Expand Down
9 changes: 5 additions & 4 deletions src/layman/upgrade/upgrade_v1_17_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from werkzeug.datastructures import FileStorage

import crs as crs_def
from db import util as db_util
from db import util as db_util, TableUri
from layman import app, settings, util as layman_util
from layman.common.prime_db_schema import publications as prime_db_schema_publications
from layman.common.filesystem import uuid as uuid_common
Expand Down Expand Up @@ -179,9 +179,10 @@ def publish_layer(workspace, layer, *, file_path, style_type, style_file, ):
if col.name not in ['wkb_geometry', 'ogc_fid']
]
source_type = qgis_util.get_source_type(db_types, qml_geometry)
layer_qml = qgis_util.fill_layer_template(workspace, layer, uuid_str, bbox, crs, qml, source_type, db_cols, table_name)
qgs_str = qgis_util.fill_project_template(workspace, layer, uuid_str, layer_qml, crs, settings.LAYMAN_OUTPUT_SRS_LIST,
bbox, source_type, table_name)
table_uri = TableUri(db_uri_str=settings.PG_URI_STR, table=table_name, schema=workspace, geo_column='wkb_geometry')
layer_qml = qgis_util.fill_layer_template(layer, uuid_str, bbox, crs, qml, source_type, db_cols, table_uri)
qgs_str = qgis_util.fill_project_template(layer, uuid_str, layer_qml, crs, settings.LAYMAN_OUTPUT_SRS_LIST,
bbox, source_type, table_uri)
with open(qgis_wms.get_layer_file_path(workspace, layer), "w") as qgs_file:
print(qgs_str, file=qgs_file)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest

from db import util as db_util, TableUri
from layman import app
from layman import app, settings
from layman.util import get_publication_info
from test_tools import process_client, external_db
from tests import EnumTestTypes, Publication
Expand All @@ -20,6 +20,7 @@
TEST_CASES = {
'all': {
'input_file_name': 'all',
'style_file': None,
'schema_name': 'public',
'table_name': 'all',
'geo_column_name': 'wkb_geometry',
Expand All @@ -29,6 +30,7 @@
},
'geometrycollection_mixed_case_table_name': {
'input_file_name': 'geometrycollection',
'style_file': None,
'schema_name': 'public',
'table_name': 'MyGeometryCollection',
'geo_column_name': 'wkb_geometry',
Expand All @@ -38,6 +40,7 @@
},
'linestring_dangerous_table_name': {
'input_file_name': 'linestring',
'style_file': None,
'schema_name': 'public',
'table_name': DANGEROUS_NAME,
'geo_column_name': 'wkb_geometry',
Expand All @@ -47,6 +50,7 @@
},
'multilinestring_dangerous_schema_name': {
'input_file_name': 'multilinestring',
'style_file': None,
'schema_name': DANGEROUS_NAME,
'table_name': 'multilinestring',
'geo_column_name': 'wkb_geometry',
Expand All @@ -56,15 +60,17 @@
},
'multipoint_dangerous_geo_column_name': {
'input_file_name': 'multipoint',
'style_file': None,
'schema_name': 'public',
'table_name': 'multipoint',
'geo_column_name': DANGEROUS_NAME,
'exp_geometry_type': 'MULTIPOINT',
'exp_native_bounding_box': [15.0, 47.8, 15.0, 48.0],
'exp_imported_into_GS': False,
},
'multipolygon': {
'multipolygon_qml': {
'input_file_name': 'multipolygon',
'style_file': 'tests/dynamic_data/publications/layer_external_db/multipolygon.qml',
'schema_name': 'public',
'table_name': 'multipolygon',
'geo_column_name': 'wkb_geometry',
Expand All @@ -74,6 +80,7 @@
},
'point': {
'input_file_name': 'point',
'style_file': None,
'schema_name': 'public',
'table_name': 'point',
'geo_column_name': 'wkb_geometry',
Expand All @@ -83,6 +90,7 @@
},
'polygon': {
'input_file_name': 'polygon',
'style_file': None,
'schema_name': 'public',
'table_name': 'polygon',
'geo_column_name': 'wkb_geometry',
Expand All @@ -109,6 +117,7 @@ class TestLayer(base_test.TestSingleRestPublication):
f"?schema={quote(value['schema_name'])}"
f"&table={quote(value['table_name'])}"
f"&geo_column={quote(value['geo_column_name'])}",
'style_file': value['style_file'],
},
params=value,
) for key, value in TEST_CASES.items()]
Expand Down Expand Up @@ -153,7 +162,11 @@ def test_layer(layer: Publication, key, rest_method, rest_args, params):
exp_thumbnail = os.path.join(DIRECTORY, f"thumbnail_{key}.png")
asserts_publ.internal.thumbnail_equals(layer.workspace, layer.type, layer.name, exp_thumbnail, max_diffs=1)
assert_util.is_publication_valid_and_complete(layer)
style_type = os.path.splitext(rest_args['style_file'])[1][1:] if rest_args['style_file'] else 'sld'
assert style_type in ['sld', 'qml']
publ_type_detail = (settings.FILE_TYPE_VECTOR, style_type)
asserts_publ.internal.correct_values_in_detail(layer.workspace, layer.type, layer.name,
publ_type_detail=publ_type_detail,
exp_publication_detail={
'bounding_box': params['exp_bounding_box'],
'native_crs': 'EPSG:4326',
Expand Down
Loading

0 comments on commit 33e6f45

Please sign in to comment.