diff --git a/CHANGELOG.md b/CHANGELOG.md index 4317bfb2a..a331f292b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - New environment variables [LAYMAN_QGIS_HOST](doc/env-settings.md#LAYMAN_QGIS_HOST), [LAYMAN_QGIS_PORT](doc/env-settings.md#LAYMAN_QGIS_PORT), and [LAYMAN_QGIS_PATH](doc/env-settings.md#LAYMAN_QGIS_PATH). - [#154](https://github.com/jirik/layman/issues/154) For endpoints [POST Layers](doc/rest.md#post-layers) and [PATCH Layer](doc/rest.md#patch-layer), parameter *sld* is replaced by the new parameter *style* and marked as deprecated. In response to endpoints [GET Layer](doc/rest.md#get-layer) and [PATCH Layer](doc/rest.md#patch-layer), *sld* is replaced by the new *style* item and marked as deprecated. - [#154](https://github.com/jirik/layman/issues/154) Also QGIS style file is accepted, previously only SLD file was. +- [#154](https://github.com/jirik/layman/issues/154) During startup, [LAYMAN_OUTPUT_SRS_LIST](doc/env-settings.md#LAYMAN_OUTPUT_SRS_LIST) is ensure for all QGIS layers. ## v1.9.1 2021-01-18 diff --git a/src/layman/__init__.py b/src/layman/__init__.py index c6c2d5d07..80ac87e59 100644 --- a/src/layman/__init__.py +++ b/src/layman/__init__.py @@ -81,7 +81,6 @@ with app.app_context(): from . import upgrade - upgrade.upgrade() app.logger.info(f'Loading Redis database') @@ -93,6 +92,11 @@ import_authn_to_redis() + app.logger.info(f'Update SRS output list for QGIS projects') + with app.app_context(): + from .layer.qgis import output_srs + output_srs.ensure_output_srs_for_all() + pipe.multi() pipe.set(LAYMAN_DEPS_ADJUSTED_KEY, 'done') pipe.execute() diff --git a/src/layman/common/geoserver/output_srs_list_test.py b/src/layman/layer/output_srs_list_test.py similarity index 54% rename from src/layman/common/geoserver/output_srs_list_test.py rename to src/layman/layer/output_srs_list_test.py index 74a2571cc..b602b35e7 100644 --- a/src/layman/common/geoserver/output_srs_list_test.py +++ b/src/layman/layer/output_srs_list_test.py @@ -1,5 +1,6 @@ import pytest -from layman import settings +from layman import settings, app +from layman.layer.qgis import util as qgis_util, wms as qgis_wms from test import process, process_client, geoserver_client @@ -19,39 +20,60 @@ def register_layer_to_delete(workspace, layername): LAYERS_TO_DELETE_AFTER_TEST.append((workspace, layername)) yield register_layer_to_delete for workspace, layername in LAYERS_TO_DELETE_AFTER_TEST: + pass process_client.delete_layer(workspace, layername) @pytest.fixture(scope="module") def ensure_layer(delete_layer_after_test): - def ensure_layer_internal(workspace, layername, file_paths=None): + def ensure_layer_internal(workspace, layername, file_paths=None, style_file=None): if (workspace, layername) not in LAYERS_TO_DELETE_AFTER_TEST: - process_client.publish_layer(workspace, layername, file_paths=file_paths) + process_client.publish_layer(workspace, layername, file_paths=file_paths, style_file=style_file) delete_layer_after_test(workspace, layername) yield ensure_layer_internal def test_custom_srs_list(ensure_layer): workspace = 'test_custom_srs_list_workspace' - layername1 = 'test_custom_srs_list_layer1' - layername2 = 'test_custom_srs_list_layer2' + layer_sld1 = 'test_custom_srs_list_sld_layer1' + layer_sld2 = 'test_custom_srs_list_sld_layer2' + layer_qgis1 = 'test_custom_srs_list_qgis_layer1' + layer_qgis2 = 'test_custom_srs_list_qgis_layer2' + source_style_file_path = 'sample/style/funny_qml.xml' assert settings.LAYMAN_OUTPUT_SRS_LIST != OUTPUT_SRS_LIST process.ensure_layman_function(process.LAYMAN_DEFAULT_SETTINGS) - ensure_layer(workspace, layername1) - assert_wms_output_srs_list(workspace, layername1, settings.LAYMAN_OUTPUT_SRS_LIST) - assert_wfs_output_srs_list(workspace, layername1, settings.LAYMAN_OUTPUT_SRS_LIST) + ensure_layer(workspace, layer_sld1) + ensure_layer(workspace, layer_qgis1, style_file=source_style_file_path) + + with app.app_context(): + assert_gs_wms_output_srs_list(workspace, layer_sld1, settings.LAYMAN_OUTPUT_SRS_LIST) + assert_wfs_output_srs_list(workspace, layer_sld1, settings.LAYMAN_OUTPUT_SRS_LIST) + assert not qgis_wms.get_layer_info(workspace, layer_sld1) + + assert_gs_wms_output_srs_list(workspace, layer_qgis1, settings.LAYMAN_OUTPUT_SRS_LIST) + assert_wfs_output_srs_list(workspace, layer_qgis1, settings.LAYMAN_OUTPUT_SRS_LIST) + assert_qgis_output_srs_list(workspace, layer_qgis1, settings.LAYMAN_OUTPUT_SRS_LIST) + assert_qgis_wms_output_srs_list(workspace, layer_qgis1, settings.LAYMAN_OUTPUT_SRS_LIST) process.ensure_layman_function({ 'LAYMAN_OUTPUT_SRS_LIST': ','.join([str(code) for code in OUTPUT_SRS_LIST]) }) - ensure_layer(workspace, layername2) - for layername in [layername1, layername2]: - assert_wms_output_srs_list(workspace, layername, OUTPUT_SRS_LIST) - assert_wfs_output_srs_list(workspace, layername, OUTPUT_SRS_LIST) - - -def assert_wms_output_srs_list(workspace, layername, expected_output_srs_list): + ensure_layer(workspace, layer_sld2) + ensure_layer(workspace, layer_qgis2, style_file=source_style_file_path) + with app.app_context(): + for layer in [layer_sld1, layer_sld2, ]: + assert_gs_wms_output_srs_list(workspace, layer, OUTPUT_SRS_LIST) + assert_wfs_output_srs_list(workspace, layer, OUTPUT_SRS_LIST) + assert not qgis_wms.get_layer_info(workspace, layer) + for layer in [layer_qgis1, layer_qgis2, ]: + assert_gs_wms_output_srs_list(workspace, layer, OUTPUT_SRS_LIST) + assert_wfs_output_srs_list(workspace, layer, OUTPUT_SRS_LIST) + assert_qgis_output_srs_list(workspace, layer, OUTPUT_SRS_LIST) + assert_qgis_wms_output_srs_list(workspace, layer, OUTPUT_SRS_LIST) + + +def assert_gs_wms_output_srs_list(workspace, layername, expected_output_srs_list): wms = geoserver_client.get_wms_capabilities(workspace) assert layername in wms.contents wms_layer = wms.contents[layername] @@ -59,6 +81,14 @@ def assert_wms_output_srs_list(workspace, layername, expected_output_srs_list): assert f"EPSG:{expected_output_srs}" in wms_layer.crsOptions +def assert_qgis_wms_output_srs_list(workspace, layer, expected_output_srs_list): + wms = qgis_wms.get_wms_capabilities(workspace, layer) + assert layer in wms.contents + wms_layer = wms.contents[layer] + for expected_output_srs in expected_output_srs_list: + assert f"EPSG:{expected_output_srs}" in wms_layer.crsOptions + + def assert_wfs_output_srs_list(workspace, layername, expected_output_srs_list): wfs = geoserver_client.get_wfs_capabilities(workspace) full_layername = f"{workspace}:{layername}" @@ -69,6 +99,11 @@ def assert_wfs_output_srs_list(workspace, layername, expected_output_srs_list): assert f"urn:ogc:def:crs:EPSG::{expected_output_srs}" in crs_names +def assert_qgis_output_srs_list(workspace, layer, expected_srs_list): + with app.app_context(): + assert qgis_util.get_layer_wms_crs_list_values(workspace, layer) == set(expected_srs_list) + + # expected coordinates manually copied from QGIS 3.16.2 in given EPSG # point_id 1: northernmost vertex of fountain at Moravske namesti, Brno @pytest.mark.parametrize('point_id, epsg_code, exp_coordinates, precision', [ @@ -79,14 +114,18 @@ def assert_wfs_output_srs_list(workspace, layername, expected_output_srs_list): (1, 32634, (179991.0748, 5458879.0878), 0.1), (1, 5514, (-598208.8093, -1160307.4484), 0.1), ]) -def test_spatial_precision(ensure_layer, point_id, epsg_code, exp_coordinates, precision): +@pytest.mark.parametrize('style_file, layer_suffix', [ + (None, '_sld', ), + ('sample/style/funny_qml.xml', '_qgis', ), +]) +def test_spatial_precision(ensure_layer, point_id, epsg_code, exp_coordinates, precision, style_file, layer_suffix, ): process.ensure_layman_function({ 'LAYMAN_OUTPUT_SRS_LIST': ','.join([str(code) for code in OUTPUT_SRS_LIST]) }) workspace = 'test_coordinate_precision_workspace' - layername = 'test_coordinate_precision_layer' + layername = 'test_coordinate_precision_layer' + layer_suffix - ensure_layer(workspace, layername, file_paths=['sample/layman.layer/sample_point_cz.geojson']) + ensure_layer(workspace, layername, file_paths=['sample/layman.layer/sample_point_cz.geojson'], style_file=style_file) feature_collection = geoserver_client.get_features(workspace, layername, epsg_code=epsg_code) feature = next(f for f in feature_collection['features'] if f['properties']['point_id'] == point_id) diff --git a/src/layman/layer/qgis/output_srs.py b/src/layman/layer/qgis/output_srs.py new file mode 100644 index 000000000..10942ec48 --- /dev/null +++ b/src/layman/layer/qgis/output_srs.py @@ -0,0 +1,11 @@ +from . import util, wms +from layman import util as layman_util, settings + + +def ensure_output_srs_for_all(): + layers = layman_util.get_publication_infos(style_type='qgis') + if layers: + (workspace, _, layer) = next(iter(layers.keys())) + if util.get_layer_wms_crs_list_values(workspace, layer) != settings.LAYMAN_OUTPUT_SRS_LIST: + for (workspace, _, layer) in layers.keys(): + wms.save_qgs_file(workspace, layer) diff --git a/src/layman/layer/qgis/wms.py b/src/layman/layer/qgis/wms.py index 7a69237e4..21129d8e0 100644 --- a/src/layman/layer/qgis/wms.py +++ b/src/layman/layer/qgis/wms.py @@ -1,4 +1,5 @@ import os +from owslib.wms import WebMapService from . import util, wms from .. import db @@ -72,3 +73,15 @@ def save_qgs_file(workspace, layer): qgs_str = util.fill_project_template(workspace, layer, uuid, layer_qml, settings.LAYMAN_OUTPUT_SRS_LIST, layer_bbox) with open(wms.get_layer_file_path(workspace, layer), "w") as qgs_file: print(qgs_str, file=qgs_file) + + +def wms_direct(wms_url, xml=None, version=None, headers=None): + from layman.layer.geoserver.wms import VERSION + version = version or VERSION + wms = WebMapService(wms_url, xml=xml.encode('utf-8') if xml is not None else xml, version=version, headers=headers) + return wms + + +def get_wms_capabilities(workspace=None, layer=None, headers=None): + wms_url = get_layer_capabilities_url(workspace, layer) + return wms_direct(wms_url, headers=headers)