diff --git a/src/geoserver/util.py b/src/geoserver/util.py
index 8d9c15b60..f833fba97 100644
--- a/src/geoserver/util.py
+++ b/src/geoserver/util.py
@@ -228,7 +228,7 @@ def patch_feature_type(geoserver_workspace, feature_type_name, *, title=None, de
if description:
ftype['abstract'] = description
if bbox:
- ftype['nativeBoundingBox'] = bbox
+ ftype['nativeBoundingBox'] = bbox_to_native_bbox(bbox)
ftype = {k: v for k, v in ftype.items() if v is not None}
body = {
@@ -500,12 +500,11 @@ def delete_wms_layer(geoserver_workspace, layer, auth):
def patch_wms_layer(geoserver_workspace, layer, *, auth, bbox):
- wms_layer = {
- "enabled": True,
- }
+ wms_layer = get_wms_layer(geoserver_workspace, layer, auth=auth)
if bbox:
- wms_layer['nativeBoundingBox'] = bbox
+ wms_layer['nativeBoundingBox'] = bbox_to_native_bbox(bbox)
wms_layer['nativeCRS'] = 'EPSG:3857'
+ # automatically recalculates also 'latLonBoundingBox'
r = requests.put(urljoin(GS_REST_WORKSPACES,
f'{geoserver_workspace}/wmslayers/{layer}'),
data=json.dumps({
@@ -518,6 +517,17 @@ def patch_wms_layer(geoserver_workspace, layer, *, auth, bbox):
r.raise_for_status()
+def get_wms_layer(geoserver_workspace, layer, *, auth):
+ r = requests.get(urljoin(GS_REST_WORKSPACES,
+ f'{geoserver_workspace}/wmslayers/{layer}'),
+ headers=headers_json,
+ auth=auth,
+ timeout=5,
+ )
+ r.raise_for_status()
+ return r.json()['wmsLayer']
+
+
def ensure_workspace(geoserver_workspace, auth=None):
auth = auth or GS_AUTH
all_workspaces = get_all_workspaces(auth)
@@ -792,3 +802,13 @@ def get_feature_type(
)
r.raise_for_status()
return r.json()['featureType']
+
+
+def bbox_to_native_bbox(bbox):
+ return {
+ "minx": bbox[0],
+ "miny": bbox[1],
+ "maxx": bbox[2],
+ "maxy": bbox[3],
+ "crs": "EPSG:3857",
+ }
diff --git a/src/layman/common/bbox.py b/src/layman/common/bbox.py
index 131fb40c0..b2aaca979 100644
--- a/src/layman/common/bbox.py
+++ b/src/layman/common/bbox.py
@@ -16,10 +16,41 @@ def contains_bbox(bbox1, bbox2):
and bbox1[0] <= bbox2[0] and bbox2[2] <= bbox1[2] and bbox1[1] <= bbox2[1] and bbox2[3] <= bbox1[3]
+def intersects(bbox1, bbox2):
+ return not is_empty(bbox1) and not is_empty(bbox2) \
+ and bbox1[0] <= bbox2[2] and bbox1[2] >= bbox2[0] and bbox1[1] <= bbox2[3] and bbox1[3] >= bbox2[1]
+
+
+def get_intersection(bbox1, bbox2):
+ intersection = [None] * 4
+ if intersects(bbox1, bbox2):
+ if bbox1[0] > bbox2[0]:
+ intersection[0] = bbox1[0]
+ else:
+ intersection[0] = bbox2[0]
+ if bbox1[1] > bbox2[1]:
+ intersection[1] = bbox1[1]
+ else:
+ intersection[1] = bbox2[1]
+ if bbox1[2] < bbox2[2]:
+ intersection[2] = bbox1[2]
+ else:
+ intersection[2] = bbox2[2]
+ if bbox1[3] < bbox2[3]:
+ intersection[3] = bbox1[3]
+ else:
+ intersection[3] = bbox2[3]
+ return intersection
+
+
def has_area(bbox):
return not is_empty(bbox) and bbox[0] != bbox[2] and bbox[1] != bbox[3]
+def get_area(bbox):
+ return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
+
+
def ensure_bbox_with_area(bbox, no_area_padding):
result = bbox
if not has_area(bbox):
@@ -46,3 +77,23 @@ def transform(bbox, epsg_from=4326, epsg_to=3857):
params = bbox + (epsg_from, epsg_to,)
result = db_util.run_query(query, params)[0]
return result
+
+
+def are_similar(bbox1, bbox2, *, no_area_bbox_padding=None, limit=0.95):
+ if not has_area(bbox1):
+ assert no_area_bbox_padding is not None and no_area_bbox_padding > 0
+ bbox1 = ensure_bbox_with_area(bbox1, no_area_bbox_padding)
+ if not has_area(bbox2):
+ assert no_area_bbox_padding is not None and no_area_bbox_padding > 0
+ bbox2 = ensure_bbox_with_area(bbox2, no_area_bbox_padding)
+ isect = get_intersection(bbox1, bbox2)
+ if is_empty(isect):
+ return False
+
+ a_area = get_area(bbox1)
+ b_area = get_area(bbox2)
+ i_area = get_area(isect)
+
+ similarity = i_area / a_area * i_area / b_area
+ # current_app.logger.info(f"a={a}, b={b}, similarity={similarity}")
+ return similarity >= limit
diff --git a/src/layman/common/bbox_test.py b/src/layman/common/bbox_test.py
index 45c74616a..4bfcf36e7 100644
--- a/src/layman/common/bbox_test.py
+++ b/src/layman/common/bbox_test.py
@@ -39,6 +39,19 @@ def test_contains_bbox(bbox1, bbox2, expected_result):
assert bbox_util.contains_bbox(bbox1, bbox2) == expected_result
+@pytest.mark.parametrize('bbox1, bbox2, expected_result', [
+ ((1, 1, 4, 4, ), (2, 2, 3, 3, ), True),
+ ((1, 1, 4, 4, ), (1, 1, 4, 4, ), True),
+ ((1, 1, 4, 4, ), (1, 1, 4, 5, ), True),
+ ((1, 1, 4, 4, ), (4, 4, 5, 5, ), True),
+ ((1, 1, 4, 4, ), (5, 5, 6, 6, ), False),
+ ((1, 1, 4, 4, ), (None, None, None, None, ), False),
+ ((None, None, None, None, ), (1, 1, 4, 4, ), False),
+])
+def test_intersects_bbox(bbox1, bbox2, expected_result):
+ assert bbox_util.intersects(bbox1, bbox2) == expected_result
+
+
@pytest.mark.parametrize('bbox, expected_result', [
((1, 1, 4, 4, ), True),
((1, 1, 1, 3, ), False),
diff --git a/src/layman/common/metadata.py b/src/layman/common/metadata.py
index 95dc58b93..9978a43ba 100644
--- a/src/layman/common/metadata.py
+++ b/src/layman/common/metadata.py
@@ -1,4 +1,6 @@
import json
+from layman import settings
+from layman.common import bbox as bbox_util
from layman.util import get_publication_types
PUBL_TYPE_DEF_KEY = __name__
@@ -10,18 +12,8 @@ def get_syncable_prop_names(publ_type):
return prop_names
-def extent_equals(a, b, limit=0.95):
- isect = _get_extent_intersetion(a, b)
- if _is_extent_empty(isect):
- return False
-
- a_area = _get_extent_area(a)
- b_area = _get_extent_area(b)
- i_area = _get_extent_area(isect)
-
- similarity = i_area / a_area * i_area / b_area
- # current_app.logger.info(f"a={a}, b={b}, similarity={similarity}")
- return similarity >= limit
+def extent_equals(a, b):
+ return bbox_util.are_similar(a, b, no_area_bbox_padding=settings.NO_AREA_BBOX_PADDING, limit=0.95)
PROPERTIES = {
@@ -142,40 +134,6 @@ def strip_capabilities_and_layers_params(url):
return strip_params_from_url(url, ['SERVICE', 'REQUEST', 'VERSION', 'LAYERS'])
-def _is_extent_empty(e):
- return any((c is None for c in e))
-
-
-def _get_extent_area(e):
- return (e[2] - e[0]) * (e[3] - e[1])
-
-
-def _get_extent_intersetion(a, b):
- intersection = [None] * 4
- if _extent_intersects(a, b):
- if a[0] > b[0]:
- intersection[0] = a[0]
- else:
- intersection[0] = b[0]
- if a[1] > b[1]:
- intersection[1] = a[1]
- else:
- intersection[1] = b[1]
- if a[2] < b[2]:
- intersection[2] = a[2]
- else:
- intersection[2] = b[2]
- if a[3] < b[3]:
- intersection[3] = a[3]
- else:
- intersection[3] = b[3]
- return intersection
-
-
-def _extent_intersects(a, b):
- return a[0] <= b[2] and a[2] >= b[0] and a[1] <= b[3] and a[3] >= b[1]
-
-
def transform_metadata_props_to_comparison(all_props):
prop_names = sorted(list(set(pn for po in all_props.values() for pn in po.keys())))
sources = {
diff --git a/src/layman/geoserver_proxy_test.py b/src/layman/geoserver_proxy_test.py
index 3e4ae4825..792b10ea3 100644
--- a/src/layman/geoserver_proxy_test.py
+++ b/src/layman/geoserver_proxy_test.py
@@ -8,6 +8,7 @@
from geoserver.util import get_layer_thumbnail, get_square_bbox
from layman import app, settings, util as layman_util
+from layman.common import bbox as bbox_util
from layman.layer import db, util as layer_util
from layman.layer.filesystem import thumbnail
from layman.layer.geoserver import wfs as geoserver_wfs
@@ -295,6 +296,8 @@ def wfs_post(workspace, attr_names_list, data_xml):
data_xml = data_wfs.get_wfs11_insert_polygon_new_attr(username, layername, attr_names10)
wfs_post(username, [(layername, attr_names10)], data_xml)
+ time.sleep(5)
+
client_util.delete_workspace_layer(username, layername, headers=headers)
client_util.delete_workspace_layer(username, layername2, headers=headers)
@@ -364,6 +367,8 @@ def do_test(wfs_query, attribute_names):
data_xml = data_wfs.get_wfs20_update_points_new_attr(username, layername1, attr_names)
do_test(data_xml, attr_names)
+ time.sleep(5)
+
client_util.delete_workspace_layer(username, layername1, headers=headers1)
@@ -375,6 +380,19 @@ def assert_all_sources_bbox(workspace, layer, expected_bbox):
test_util.assert_wfs_bbox(workspace, layer, expected_bbox)
test_util.assert_wms_bbox(workspace, layer, expected_bbox)
+ with app.app_context():
+ expected_bbox_4326 = bbox_util.transform(expected_bbox, 3857, 4326, )
+ md_comparison = client_util.get_workspace_layer_metadata_comparison(workspace, layer)
+ csw_prefix = settings.CSW_PROXY_URL
+ csw_src_key = client_util.get_source_key_from_metadata_comparison(md_comparison, csw_prefix)
+ assert csw_src_key is not None
+ prop_key = 'extent'
+ md_props = md_comparison['metadata_properties']
+ assert md_props[prop_key]['equal'] is True
+ assert md_props[prop_key]['equal_or_null'] is True
+ csw_bbox_4326 = tuple(md_props[prop_key]['values'][csw_src_key])
+ test_util.assert_same_bboxes(expected_bbox_4326, csw_bbox_4326, 0.001)
+
@pytest.mark.parametrize('style_file, thumbnail_style_postfix', [
(None, '_sld'),
diff --git a/src/layman/layer/geoserver/__init__.py b/src/layman/layer/geoserver/__init__.py
index 6659bf0b5..4da370688 100644
--- a/src/layman/layer/geoserver/__init__.py
+++ b/src/layman/layer/geoserver/__init__.py
@@ -7,7 +7,7 @@
from layman.http import LaymanError
from layman import settings, util as layman_util
from layman.common import bbox as bbox_util, geoserver as gs_common, empty_method
-from layman.layer import LAYER_TYPE, db as db_source
+from layman.layer import LAYER_TYPE
from layman.layer.qgis import wms as qgis_wms
from . import wms
@@ -108,25 +108,15 @@ def set_security_rules(workspace, layer, access_rights, auth, geoserver_workspac
gs_util.ensure_layer_security_roles(geoserver_workspace, layer, security_write_roles, 'w', auth)
-def bbox_to_native_bbox(bbox):
- return {
- "minx": bbox[0],
- "miny": bbox[1],
- "maxx": bbox[2],
- "maxy": bbox[3],
- "crs": "EPSG:3857",
- }
-
-
-def get_default_native_bbox():
- return bbox_to_native_bbox(settings.LAYMAN_DEFAULT_OUTPUT_BBOX)
+def get_layer_bbox(workspace, layer):
+ db_bbox = layman_util.get_publication_info(workspace, LAYER_TYPE, layer, context={'keys': ['bounding_box']})['bounding_box']
+ # GeoServer is not working good with degradeted bbox
+ return bbox_util.ensure_bbox_with_area(db_bbox, settings.NO_AREA_BBOX_PADDING) if not bbox_util.is_empty(db_bbox) else settings.LAYMAN_DEFAULT_OUTPUT_BBOX
def get_layer_native_bbox(workspace, layer):
- db_bbox = layman_util.get_publication_info(workspace, LAYER_TYPE, layer, context={'keys': ['bounding_box']})['bounding_box']
- # GeoServer is not working good with degradeted bbox
- bbox = bbox_util.ensure_bbox_with_area(db_bbox, settings.NO_AREA_BBOX_PADDING) if not bbox_util.is_empty(db_bbox) else settings.LAYMAN_DEFAULT_OUTPUT_BBOX
- return bbox_to_native_bbox(bbox)
+ bbox = get_layer_bbox(workspace, layer)
+ return gs_util.bbox_to_native_bbox(bbox)
def publish_layer_from_db(workspace, layername, description, title, access_rights, geoserver_workspace=None):
diff --git a/src/layman/layer/geoserver/wfs_tasks.py b/src/layman/layer/geoserver/wfs_tasks.py
index 0206cc4cf..14b6c488d 100644
--- a/src/layman/layer/geoserver/wfs_tasks.py
+++ b/src/layman/layer/geoserver/wfs_tasks.py
@@ -20,7 +20,7 @@ def patch_after_wfst(
if self.is_aborted():
raise AbortedException
- bbox = geoserver.get_layer_native_bbox(workspace, layer)
+ bbox = geoserver.get_layer_bbox(workspace, layer)
gs_util.patch_feature_type(workspace, layer, auth=settings.LAYMAN_GS_AUTH, bbox=bbox)
wfs.clear_cache(workspace)
diff --git a/src/layman/layer/geoserver/wms_tasks.py b/src/layman/layer/geoserver/wms_tasks.py
index ad75ba23b..2aaf4fcbb 100644
--- a/src/layman/layer/geoserver/wms_tasks.py
+++ b/src/layman/layer/geoserver/wms_tasks.py
@@ -20,7 +20,7 @@ def patch_after_wfst(
if self.is_aborted():
raise AbortedException
- bbox = geoserver.get_layer_native_bbox(workspace, layer)
+ bbox = geoserver.get_layer_bbox(workspace, layer)
geoserver_workspace = wms.get_geoserver_workspace(workspace)
style_type = layman_util.get_publication_info(workspace, LAYER_TYPE, layer, context={'keys': ['style_type'], })['style_type']
if style_type == 'sld':
diff --git a/src/layman/layer/micka/csw.py b/src/layman/layer/micka/csw.py
index 80ad834d0..f1292ded9 100644
--- a/src/layman/layer/micka/csw.py
+++ b/src/layman/layer/micka/csw.py
@@ -8,7 +8,7 @@
from layman.common.filesystem.uuid import get_publication_uuid_file
from layman.common.micka import util as common_util
-from layman.common import language as common_language, empty_method, empty_method_returns_none
+from layman.common import language as common_language, empty_method, empty_method_returns_none, bbox as bbox_util
from layman.layer.filesystem.uuid import get_layer_uuid
from layman.layer import db
from layman.layer.geoserver import wms
@@ -114,19 +114,15 @@ def csw_insert(workspace, layername):
def get_template_path_and_values(workspace, layername, http_method=None):
assert http_method in ['post', 'patch']
- wmsi = wms.get_wms_proxy(workspace)
- wms_layer = wmsi.contents.get(layername) if wmsi else None
publ_info = get_publication_info(workspace, LAYER_TYPE, layername, context={
- 'keys': ['title'],
+ 'keys': ['title', 'bounding_box', 'description'],
})
title = publ_info['title']
-
- if wms_layer:
- abstract = wms_layer.abstract
- extent = wms_layer.boundingBoxWGS84
- else:
- abstract = None
- extent = [-180, -90, 180, 90]
+ abstract = publ_info.get('description')
+ bbox_3857 = publ_info.get('bounding_box')
+ if bbox_util.is_empty(bbox_3857):
+ bbox_3857 = settings.LAYMAN_DEFAULT_OUTPUT_BBOX
+ extent = bbox_util.transform(tuple(bbox_3857), epsg_from=3857, epsg_to=4326)
uuid_file_path = get_publication_uuid_file(LAYER_TYPE, workspace, layername)
publ_datetime = datetime.fromtimestamp(os.path.getmtime(uuid_file_path))
diff --git a/src/layman/layer/micka/soap_tasks.py b/src/layman/layer/micka/soap_tasks.py
new file mode 100644
index 000000000..858a43065
--- /dev/null
+++ b/src/layman/layer/micka/soap_tasks.py
@@ -0,0 +1,26 @@
+from celery.utils.log import get_task_logger
+
+from layman.celery import AbortedException
+from layman import celery_app
+from . import soap
+
+logger = get_task_logger(__name__)
+
+
+@celery_app.task(
+ name='layman.layer.micka.soap.patch_after_wfst',
+ bind=True,
+ base=celery_app.AbortableTask
+)
+def patch_after_wfst(
+ self,
+ workspace,
+ layer,
+):
+ if self.is_aborted():
+ raise AbortedException
+
+ soap.patch_layer(workspace, layer, metadata_properties_to_refresh=['extent'])
+
+ if self.is_aborted():
+ raise AbortedException
diff --git a/src/layman/layer/rest_test_filled_template.xml b/src/layman/layer/rest_test_filled_template.xml
index c20b5a0a8..08d46a5eb 100644
--- a/src/layman/layer/rest_test_filled_template.xml
+++ b/src/layman/layer/rest_test_filled_template.xml
@@ -245,16 +245,16 @@
- -179.9999999999996
+ -180.0
180
- -85.60903777459771
+ -85.6090377745977
- 83.64512999999998
+ 83.64513
diff --git a/src/layman/map/micka/csw.py b/src/layman/map/micka/csw.py
index 4d8eaeb7a..7c127c680 100644
--- a/src/layman/map/micka/csw.py
+++ b/src/layman/map/micka/csw.py
@@ -9,7 +9,7 @@
from flask import current_app
from layman import settings, LaymanError
-from layman.common import language as common_language, empty_method, empty_method_returns_none
+from layman.common import language as common_language, empty_method, empty_method_returns_none, bbox as bbox_util
from layman.common.filesystem.uuid import get_publication_uuid_file
from layman.common.micka import util as common_util
from layman.map import MAP_TYPE
@@ -182,23 +182,32 @@ def get_template_path_and_values(username, mapname, http_method=None, actor_name
revision_date = datetime.now()
map_json = get_map_json(username, mapname)
operates_on = map_json_to_operates_on(map_json, editor=actor_name)
+ publ_info = get_publication_info(username, MAP_TYPE, mapname, context={
+ 'keys': ['title', 'bounding_box', 'description'],
+ })
+ bbox_3857 = publ_info.get('bounding_box')
+ if bbox_util.is_empty(bbox_3857):
+ bbox_3857 = settings.LAYMAN_DEFAULT_OUTPUT_BBOX
+ extent = bbox_util.transform(tuple(bbox_3857), epsg_from=3857, epsg_to=4326)
+ title = publ_info['title']
+ abstract = publ_info.get('description')
md_language = next(iter(common_language.get_languages_iso639_2(' '.join([
- map_json['title'] or '',
- map_json['abstract'] or ''
+ title or '',
+ abstract or '',
]))), None)
prop_values = _get_property_values(
username=username,
mapname=mapname,
uuid=get_map_uuid(username, mapname),
- title=map_json['title'],
- abstract=map_json['abstract'] or None,
+ title=title,
+ abstract=abstract or None,
publication_date=publ_datetime.strftime('%Y-%m-%d'),
revision_date=revision_date.strftime('%Y-%m-%d'),
md_date_stamp=date.today().strftime('%Y-%m-%d'),
identifier=url_for('rest_workspace_map.get', workspace=username, mapname=mapname),
identifier_label=mapname,
- extent=[float(c) for c in map_json['extent']],
+ extent=extent,
epsg_codes=map_json_to_epsg_codes(map_json),
md_organisation_name=None,
organisation_name=None,
diff --git a/src/layman/map/rest_test_filled_template.xml b/src/layman/map/rest_test_filled_template.xml
index 83fff4ebc..013807215 100644
--- a/src/layman/map/rest_test_filled_template.xml
+++ b/src/layman/map/rest_test_filled_template.xml
@@ -177,7 +177,7 @@
-48.5
- 81.5
+ 81.4999999999999
diff --git a/test/util.py b/test/util.py
index be858d775..01921df6d 100644
--- a/test/util.py
+++ b/test/util.py
@@ -92,6 +92,13 @@ def assert_wfs_bbox(workspace, layer, expected_bbox):
def assert_wms_bbox(workspace, layer, expected_bbox):
with app.app_context():
wms_get_capabilities = wms.get_wms_proxy(workspace)
- wms_bboxes = wms_get_capabilities.contents[layer].crs_list
- wms_bbox_3857 = next(bbox[:4] for bbox in wms_bboxes if bbox[4] == 'EPSG:3857')
- assert_same_bboxes(expected_bbox, wms_bbox_3857, 0.00001)
+ wms_layer = wms_get_capabilities.contents[layer]
+ bbox_3857 = next(bbox[:4] for bbox in wms_layer.crs_list if bbox[4] == 'EPSG:3857')
+ assert_same_bboxes(expected_bbox, bbox_3857, 0.00001)
+
+ with app.app_context():
+ expected_bbox_4326 = bbox_util.transform(expected_bbox, 3857, 4326, )
+ wgs84_bboxes = [bbox[:4] for bbox in wms_layer.crs_list if bbox[4] in ['EPSG:4326', 'CRS:84']]
+ wgs84_bboxes.append(wms_layer.boundingBoxWGS84)
+ for wgs84_bbox in wgs84_bboxes:
+ assert_same_bboxes(expected_bbox_4326, wgs84_bbox, 0.00001)