From ddd26678fc434fceffa1d31b8e44d54b95f1053d Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 27 Jan 2026 15:03:46 +0100 Subject: [PATCH 1/3] feat(attribute_values): attribute_metadata added to attribute_names blueprints for meshes --- .../routes/blueprint_routes.py | 49 ++++++++++++++----- tests/test_routes.py | 18 +++++++ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 570ef41..a383bb3 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -264,6 +264,28 @@ def texture_coordinates() -> flask.Response: return flask.make_response({"texture_coordinates": texture_coordinates}, 200) +def attributes_metadata(manager: Any) -> dict[str, Any]: + metadata: dict[str, Any] = {} + for name in manager.attribute_names(): + attribute = manager.find_generic_attribute(name) + if not attribute.is_genericable(): + metadata[name] = [-1, -1] + continue + min_value = None + max_value = None + nb_items = attribute.nb_items() + for i in range(nb_items): + generic_value = attribute.generic_value(i) + if not isinstance(generic_value, (int, float)): + continue + if min_value is None or generic_value < min_value: + min_value = generic_value + if max_value is None or generic_value > max_value: + max_value = generic_value + metadata[name] = [min_value, max_value] if min_value is not None else [-1, -1] + return metadata + + @routes.route( schemas_dict["vertex_attribute_names"]["route"], methods=schemas_dict["vertex_attribute_names"]["methods"], @@ -276,10 +298,11 @@ def vertex_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeMesh): flask.abort(400, f"{params.id} is not a GeodeMesh") - vertex_attribute_names = geode_object.vertex_attribute_manager().attribute_names() + attribute_manager = geode_object.vertex_attribute_manager() return flask.make_response( { - "vertex_attribute_names": vertex_attribute_names, + "vertex_attribute_names": attribute_manager.attribute_names(), + "vertex_attribute_metadata": attributes_metadata(attribute_manager), }, 200, ) @@ -297,10 +320,11 @@ def cell_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeGrid2D | GeodeGrid3D): flask.abort(400, f"{params.id} is not a GeodeGrid") - cell_attribute_names = geode_object.cell_attribute_manager().attribute_names() + attribute_manager = geode_object.cell_attribute_manager() return flask.make_response( { - "cell_attribute_names": cell_attribute_names, + "cell_attribute_names": attribute_manager.attribute_names(), + "cell_attribute_metadata": attributes_metadata(attribute_manager), }, 200, ) @@ -318,10 +342,11 @@ def polygon_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeSurfaceMesh2D | GeodeSurfaceMesh3D): flask.abort(400, f"{params.id} is not a GeodeSurfaceMesh") - polygon_attribute_names = geode_object.polygon_attribute_manager().attribute_names() + attribute_manager = geode_object.polygon_attribute_manager() return flask.make_response( { - "polygon_attribute_names": polygon_attribute_names, + "polygon_attribute_names": attribute_manager.attribute_names(), + "polygon_attribute_metadata": attributes_metadata(attribute_manager), }, 200, ) @@ -339,12 +364,11 @@ def polyhedron_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeSolidMesh3D): flask.abort(400, f"{params.id} is not a GeodeSolidMesh") - polyhedron_attribute_names = ( - geode_object.polyhedron_attribute_manager().attribute_names() - ) + attributemanager = geode_object.polyhedron_attribute_manager() return flask.make_response( { - "polyhedron_attribute_names": polyhedron_attribute_names, + "polyhedron_attribute_names": attributemanager.attribute_names(), + "polyhedron_attribute_metadata": attributes_metadata(attributemanager), }, 200, ) @@ -362,10 +386,11 @@ def edge_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeGraph): flask.abort(400, f"{params.id} does not have edges") - edge_attribute_names = geode_object.edge_attribute_manager().attribute_names() + attribute_manager = geode_object.edge_attribute_manager() return flask.make_response( { - "edge_attribute_names": edge_attribute_names, + "edge_attribute_names": attribute_manager.attribute_names(), + "edge_attribute_metadata": attributes_metadata(attribute_manager), }, 200, ) diff --git a/tests/test_routes.py b/tests/test_routes.py index 4c84f32..2e2e940 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -245,9 +245,12 @@ def test_vertex_attribute_names(client: FlaskClient, test_id: str) -> None: response = client.post(route, json={"id": data.id}) assert response.status_code == 200 vertex_attribute_names = response.get_json()["vertex_attribute_names"] + vertex_attribute_metadata = response.get_json()["vertex_attribute_metadata"] assert type(vertex_attribute_names) is list + assert type(vertex_attribute_metadata) is dict for vertex_attribute_name in vertex_attribute_names: assert type(vertex_attribute_name) is str + assert vertex_attribute_name in vertex_attribute_metadata def test_cell_attribute_names(client: FlaskClient, test_id: str) -> None: @@ -271,9 +274,12 @@ def test_cell_attribute_names(client: FlaskClient, test_id: str) -> None: response = client.post(route, json={"id": data.id}) assert response.status_code == 200 cell_attribute_names = response.get_json()["cell_attribute_names"] + cell_attribute_metadata = response.get_json()["cell_attribute_metadata"] assert type(cell_attribute_names) is list + assert type(cell_attribute_metadata) is dict for cell_attribute_name in cell_attribute_names: assert type(cell_attribute_name) is str + assert cell_attribute_name in cell_attribute_metadata def test_polygon_attribute_names(client: FlaskClient, test_id: str) -> None: @@ -297,9 +303,12 @@ def test_polygon_attribute_names(client: FlaskClient, test_id: str) -> None: response = client.post(route, json={"id": data.id}) assert response.status_code == 200 polygon_attribute_names = response.get_json()["polygon_attribute_names"] + polygon_attribute_metadata = response.get_json()["polygon_attribute_metadata"] assert type(polygon_attribute_names) is list + assert type(polygon_attribute_metadata) is dict for polygon_attribute_name in polygon_attribute_names: assert type(polygon_attribute_name) is str + assert polygon_attribute_name in polygon_attribute_metadata def test_polyhedron_attribute_names(client: FlaskClient, test_id: str) -> None: @@ -324,9 +333,15 @@ def test_polyhedron_attribute_names(client: FlaskClient, test_id: str) -> None: print(response.get_json()) assert response.status_code == 200 polyhedron_attribute_names = response.get_json()["polyhedron_attribute_names"] + polyhedron_attribute_metadata = response.get_json()["polyhedron_attribute_metadata"] assert type(polyhedron_attribute_names) is list + assert type(polyhedron_attribute_metadata) is dict for polyhedron_attribute_name in polyhedron_attribute_names: assert type(polyhedron_attribute_name) is str + assert polyhedron_attribute_name in polyhedron_attribute_metadata + + if "Range" in polyhedron_attribute_metadata: + assert polyhedron_attribute_metadata["Range"] == [0, 579] def test_edge_attribute_names(client: FlaskClient, test_id: str) -> None: @@ -351,9 +366,12 @@ def test_edge_attribute_names(client: FlaskClient, test_id: str) -> None: print(response.get_json()) assert response.status_code == 200 edge_attribute_names = response.get_json()["edge_attribute_names"] + edge_attribute_metadata = response.get_json()["edge_attribute_metadata"] assert type(edge_attribute_names) is list + assert type(edge_attribute_metadata) is dict for edge_attribute_name in edge_attribute_names: assert type(edge_attribute_name) is str + assert edge_attribute_name in edge_attribute_metadata def test_database_uri_path(client: FlaskClient) -> None: From dae9c2d6cd6482a4d037766d17463ae398c81400 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:06:57 +0000 Subject: [PATCH 2/3] Apply prepare changes --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a596a0a..08d64bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,4 +60,3 @@ werkzeug==3.1.2 # flask # flask-cors -opengeodeweb-microservice==1.*,>=1.0.13rc1 From 999b39d0b69efad386f2b929fa3a4c5ac1dddbe6 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 27 Jan 2026 16:56:30 +0100 Subject: [PATCH 3/3] float and type attribute_metadata --- .../routes/blueprint_routes.py | 21 +++++++++++-------- tests/test_routes.py | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index a383bb3..88ab768 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -8,6 +8,7 @@ import flask import werkzeug import zipfile +import opengeode as og import opengeode_io as og_io import opengeode_geosciences as og_geosciences import opengeode_geosciencesio as og_geosciencesio @@ -264,25 +265,27 @@ def texture_coordinates() -> flask.Response: return flask.make_response({"texture_coordinates": texture_coordinates}, 200) -def attributes_metadata(manager: Any) -> dict[str, Any]: - metadata: dict[str, Any] = {} +def attributes_metadata(manager: og.AttributeManager) -> dict[str, list[float]]: + metadata: dict[str, list[float]] = {} for name in manager.attribute_names(): attribute = manager.find_generic_attribute(name) if not attribute.is_genericable(): - metadata[name] = [-1, -1] + metadata[name] = [-1.0, -1.0] continue min_value = None max_value = None nb_items = attribute.nb_items() for i in range(nb_items): generic_value = attribute.generic_value(i) - if not isinstance(generic_value, (int, float)): - continue if min_value is None or generic_value < min_value: min_value = generic_value if max_value is None or generic_value > max_value: max_value = generic_value - metadata[name] = [min_value, max_value] if min_value is not None else [-1, -1] + metadata[name] = ( + [min_value, max_value] + if min_value is not None and max_value is not None + else [-1.0, -1.0] + ) return metadata @@ -364,11 +367,11 @@ def polyhedron_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeSolidMesh3D): flask.abort(400, f"{params.id} is not a GeodeSolidMesh") - attributemanager = geode_object.polyhedron_attribute_manager() + attribute_manager = geode_object.polyhedron_attribute_manager() return flask.make_response( { - "polyhedron_attribute_names": attributemanager.attribute_names(), - "polyhedron_attribute_metadata": attributes_metadata(attributemanager), + "polyhedron_attribute_names": attribute_manager.attribute_names(), + "polyhedron_attribute_metadata": attributes_metadata(attribute_manager), }, 200, ) diff --git a/tests/test_routes.py b/tests/test_routes.py index 2e2e940..d40c68a 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -341,7 +341,7 @@ def test_polyhedron_attribute_names(client: FlaskClient, test_id: str) -> None: assert polyhedron_attribute_name in polyhedron_attribute_metadata if "Range" in polyhedron_attribute_metadata: - assert polyhedron_attribute_metadata["Range"] == [0, 579] + assert polyhedron_attribute_metadata["Range"] == [0.0, 579.0] def test_edge_attribute_names(client: FlaskClient, test_id: str) -> None: