From 24a9c11df73040d1b6add26add43ee471690e1f2 Mon Sep 17 00:00:00 2001 From: SpliiT Date: Wed, 29 Oct 2025 14:24:10 +0100 Subject: [PATCH 1/8] feat(create): New feature : create Volume of Interest --- .../routes/create/blueprint_create.py | 68 +++++++++++- .../routes/create/schemas/__init__.py | 1 + .../routes/create/schemas/create_voi.json | 35 ++++++ .../routes/create/schemas/create_voi.py | 20 ++++ tests/test_create_routes.py | 100 ++++++++++++++++++ 5 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 src/opengeodeweb_back/routes/create/schemas/create_voi.json create mode 100644 src/opengeodeweb_back/routes/create/schemas/create_voi.py diff --git a/src/opengeodeweb_back/routes/create/blueprint_create.py b/src/opengeodeweb_back/routes/create/blueprint_create.py index 2398833d..ff05670d 100644 --- a/src/opengeodeweb_back/routes/create/blueprint_create.py +++ b/src/opengeodeweb_back/routes/create/blueprint_create.py @@ -54,7 +54,7 @@ def create_aoi() -> flask.Response: # Create vertices first for point in params.points: - pp = opengeode.Point3D([point.x, point.y, params.z]) + # pp = opengeode.Point3D([point.x, point.y, params.z]) builder.create_point(opengeode.Point3D([point.x, point.y, params.z])) # Create edges between consecutive vertices and close the loop @@ -69,3 +69,69 @@ def create_aoi() -> flask.Response: data=edged_curve, ) return flask.make_response(result, 200) + + +@routes.route( + schemas_dict["create_voi"]["route"], methods=schemas_dict["create_voi"]["methods"] +) +def create_voi() -> flask.Response: + """Endpoint to create a Volume of Interest (VOI) as an EdgedCurve3D (a bounding box).""" + print(f"create_voi : {flask.request=}", flush=True) + utils_functions.validate_request(flask.request, schemas_dict["create_voi"]) + params = schemas.CreateVoi.from_dict(flask.request.get_json()) + + # 1. Simuler la récupération des coordonnées (X, Y) de l'AOI + # ATTENTION : En l'absence de `utils_functions.get_data`, nous utilisons des + # points de simulation pour construire la VOI. L'ID de l'AOI (params.aoi_id) est ignoré. + aoi_vertices = [ + (0.0, 0.0), + (10.0, 0.0), + (10.0, 10.0), + (0.0, 10.0), + ] + + # 2. Créer le VOI (EdgedCurve3D) + edged_curve = geode_functions.geode_object_class("EdgedCurve3D").create() + builder = geode_functions.create_builder("EdgedCurve3D", edged_curve) + builder.set_name(params.name) + + z_min = params.z_min + z_max = params.z_max + + # 3. Créer les 8 vertices de la boîte (VOI) + # Indices 0-3 (face inférieure Z_min), Indices 4-7 (face supérieure Z_max) + + # Bottom face (Z_min) indices 0, 1, 2, 3 + for x, y in aoi_vertices: + builder.create_point(opengeode.Point3D([x, y, z_min])) + + # Top face (Z_max) indices 4, 5, 6, 7 + for x, y in aoi_vertices: + builder.create_point(opengeode.Point3D([x, y, z_max])) + + # 4. Créer les 12 arêtes + + # Arêtes de la face inférieure: 0-1, 1-2, 2-3, 3-0 + bottom_edges = [(i, (i + 1) % 4) for i in range(4)] + + # Arêtes de la face supérieure: 4-5, 5-6, 6-7, 7-4 + top_edges = [(i + 4, (i + 1) % 4 + 4) for i in range(4)] + + # Arêtes verticales: 0-4, 1-5, 2-6, 3-7 + vertical_edges = [(i, i + 4) for i in range(4)] + + all_edges = bottom_edges + top_edges + vertical_edges + + for v1, v2 in all_edges: + builder.create_edge_with_vertices(v1, v2) + + # 5. Sauvegarder et obtenir les informations + # Utilise generate_native_viewable... car update_native_viewable... n'est pas disponible. + # L'ID optionnel (params.id) est donc ignoré, et une NOUVELLE entrée Data est créée. + result = utils_functions.generate_native_viewable_and_light_viewable_from_object( + geode_object="EdgedCurve3D", + data=edged_curve, + ) + + # Retourne l'ID de la nouvelle entrée Data créée + return flask.make_response(result, 200) \ No newline at end of file diff --git a/src/opengeodeweb_back/routes/create/schemas/__init__.py b/src/opengeodeweb_back/routes/create/schemas/__init__.py index c9a26ab0..6a710590 100644 --- a/src/opengeodeweb_back/routes/create/schemas/__init__.py +++ b/src/opengeodeweb_back/routes/create/schemas/__init__.py @@ -1,2 +1,3 @@ from .create_point import * from .create_aoi import * +from .create_voi import * diff --git a/src/opengeodeweb_back/routes/create/schemas/create_voi.json b/src/opengeodeweb_back/routes/create/schemas/create_voi.json new file mode 100644 index 00000000..bf3f3c50 --- /dev/null +++ b/src/opengeodeweb_back/routes/create/schemas/create_voi.json @@ -0,0 +1,35 @@ +{ + "route": "/create_voi", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the VOI" + }, + "aoi_id": { + "type": "string", + "description": "ID of the corresponding AOI from which to take X and Y coordinates" + }, + "z_min": { + "type": "number", + "description": "Minimum Z coordinate for the VOI" + }, + "z_max": { + "type": "number", + "description": "Maximum Z coordinate for the VOI" + }, + "id": { + "type": "string" + } + }, + "required": [ + "name", + "aoi_id", + "z_min", + "z_max" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/src/opengeodeweb_back/routes/create/schemas/create_voi.py b/src/opengeodeweb_back/routes/create/schemas/create_voi.py new file mode 100644 index 00000000..0bd492b0 --- /dev/null +++ b/src/opengeodeweb_back/routes/create/schemas/create_voi.py @@ -0,0 +1,20 @@ +from dataclasses_json import DataClassJsonMixin +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class CreateVoi(DataClassJsonMixin): + name: str + """Name of the VOI""" + + aoi_id: str + """ID of the corresponding AOI from which to take X and Y coordinates""" + + z_min: float + """Minimum Z coordinate for the VOI""" + + z_max: float + """Maximum Z coordinate for the VOI""" + + id: Optional[str] = None \ No newline at end of file diff --git a/tests/test_create_routes.py b/tests/test_create_routes.py index ec6f6d8a..3e823ede 100644 --- a/tests/test_create_routes.py +++ b/tests/test_create_routes.py @@ -29,6 +29,17 @@ def aoi_data() -> Dict[str, Any]: "z": 0.0, } +@pytest.fixture +def voi_data() -> Dict[str, Any]: + """Fixture for Volume of Interest (VOI) test data.""" + return { + "name": "test_voi", + "aoi_id": str(uuid.uuid4()), + "z_min": -50.0, + "z_max": 100.0, + "id": str(uuid.uuid4()), + } + def test_create_point(client: FlaskClient, point_data: Dict[str, Any]) -> None: """Test the creation of a point with valid data.""" @@ -80,6 +91,33 @@ def test_create_aoi(client: FlaskClient, aoi_data: Dict[str, Any]) -> None: test_utils.test_route_wrong_params(client, route, lambda: aoi_data.copy()) # type: ignore +def test_create_voi(client: FlaskClient, voi_data: Dict[str, Any]) -> None: + """Test the creation of a VOI with valid data (including optional id).""" + route: str = "/opengeodeweb_back/create/create_voi" + + response = client.post(route, json=voi_data) + assert response.status_code == 200 + + response_data: Any = response.json + assert "id" in response_data + assert "name" in response_data + assert response_data["name"] == voi_data["name"] + assert response_data["object_type"] == "mesh" + assert response_data["geode_object"] == "EdgedCurve3D" + + voi_data_no_id = voi_data.copy() + del voi_data_no_id["id"] + response = client.post(route, json=voi_data_no_id) + assert response.status_code == 200 + assert response.json["name"] == voi_data["name"] + + voi_data_required_only = voi_data.copy() + del voi_data_required_only["id"] + + test_utils.test_route_wrong_params(client, route, lambda: voi_data_required_only.copy()) # type: ignore + + + def test_create_point_with_invalid_data(client: FlaskClient) -> None: """Test the point creation endpoint with invalid data.""" route: str = "/opengeodeweb_back/create/create_point" @@ -130,6 +168,28 @@ def test_create_aoi_with_invalid_data( assert response.status_code == 400 +def test_create_voi_with_invalid_data( + client: FlaskClient, voi_data: Dict[str, Any] +) -> None: + """Test the VOI creation endpoint with invalid data.""" + route: str = "/opengeodeweb_back/create/create_voi" + + # Test with non-numeric z_min + invalid_data: Dict[str, Any] = {**voi_data, "z_min": "not_a_number"} + response = client.post(route, json=invalid_data) + assert response.status_code == 400 + + # Test with non-numeric z_max + invalid_data = {**voi_data, "z_max": "not_a_number"} + response = client.post(route, json=invalid_data) + assert response.status_code == 400 + + # Test with invalid aoi_id format (e.g., not a string/uuid) + invalid_data = {**voi_data, "aoi_id": 12345} + response = client.post(route, json=invalid_data) + assert response.status_code == 400 + + def test_create_point_file_generation( client: FlaskClient, point_data: Dict[str, Any] ) -> None: @@ -208,3 +268,43 @@ def test_create_aoi_file_generation( # Verify file extensions assert response_data["native_file_name"].endswith(".og_edc3d") assert response_data["viewable_file_name"].endswith(".vtp") + + +def test_create_voi_file_generation( + client: FlaskClient, voi_data: Dict[str, Any] +) -> None: + """Test that the VOI creation generates the correct files.""" + route: str = "/opengeodeweb_back/create/create_voi" + + # Make the request + response = client.post(route, json=voi_data) + assert response.status_code == 200 + response_data: Any = response.json + + # Get the data folder path for this specific ID + DATA_FOLDER_PATH: str = client.application.config["DATA_FOLDER_PATH"] + data_id: str = response_data["id"] + data_folder: str = os.path.join(DATA_FOLDER_PATH, data_id) + + # Check that the data folder exists + assert os.path.exists(data_folder) + assert os.path.isdir(data_folder) + + # Check native file exists + native_file_path: str = os.path.join(data_folder, response_data["native_file_name"]) + assert os.path.exists(native_file_path) + + # Check viewable file exists + viewable_file_path: str = os.path.join( + data_folder, response_data["viewable_file_name"] + ) + assert os.path.exists(viewable_file_path) + + # Check light viewable file exists if present + if "binary_light_viewable" in response_data: + light_viewable_file_path: str = os.path.join(data_folder, "light_viewable.vtp") + assert os.path.exists(light_viewable_file_path) + + # Verify file extensions (VOI uses EdgedCurve3D like AOI) + assert response_data["native_file_name"].endswith(".og_edc3d") + assert response_data["viewable_file_name"].endswith(".vtp") From ad9c5f09d4f6dcb4734ea798b30330263b915bf2 Mon Sep 17 00:00:00 2001 From: SpliiT Date: Fri, 31 Oct 2025 17:47:51 +0100 Subject: [PATCH 2/8] feat(create): New feature : create Volume of Interest --- opengeodeweb_back_schemas.json | 36 ++ schemas.json | 348 ------------------ .../routes/create/blueprint_create.py | 57 ++- .../routes/create/schemas/create_voi.json | 29 +- .../routes/create/schemas/create_voi.py | 17 +- 5 files changed, 102 insertions(+), 385 deletions(-) delete mode 100644 schemas.json diff --git a/opengeodeweb_back_schemas.json b/opengeodeweb_back_schemas.json index ee3d0db3..ceb3e941 100644 --- a/opengeodeweb_back_schemas.json +++ b/opengeodeweb_back_schemas.json @@ -77,6 +77,42 @@ "z" ], "additionalProperties": false + }, + "create_voi": { + "$id": "opengeodeweb_back/create/create_voi", + "route": "/create_voi", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the VOI" + }, + "aoi_id": { + "type": "string", + "description": "ID of the corresponding AOI from which to take X and Y coordinates" + }, + "z_min": { + "type": "number", + "description": "Minimum Z coordinate for the VOI" + }, + "z_max": { + "type": "number", + "description": "Maximum Z coordinate for the VOI" + }, + "id": { + "type": "string" + } + }, + "required": [ + "name", + "aoi_id", + "z_min", + "z_max" + ], + "additionalProperties": false } }, "models": { diff --git a/schemas.json b/schemas.json deleted file mode 100644 index 01bba35c..00000000 --- a/schemas.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "opengeodeweb_back": { - "models": { - "vtm_component_indices": { - "$id": "opengeodeweb_back/models/vtm_component_indices", - "route": "/vtm_component_indices", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": [ - "id" - ], - "additionalProperties": false - }, - "mesh_components": { - "$id": "opengeodeweb_back/models/mesh_components", - "route": "/mesh_components", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "filename": { - "type": "string" - }, - "geode_object": { - "type": "string" - } - }, - "required": [ - "filename", - "geode_object" - ], - "additionalProperties": false - } - }, - "vertex_attribute_names": { - "$id": "opengeodeweb_back/vertex_attribute_names", - "route": "/vertex_attribute_names", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "input_geode_object": { - "type": "string", - "minLength": 1 - }, - "filename": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "input_geode_object", - "filename" - ], - "additionalProperties": false - }, - "upload_file": { - "$id": "opengeodeweb_back/upload_file", - "route": "/upload_file", - "methods": [ - "OPTIONS", - "PUT" - ], - "type": "object", - "properties": { - "filename": { - "type": "string", - "minLength": 1 - } - }, - "additionalProperties": false - }, - "texture_coordinates": { - "$id": "opengeodeweb_back/texture_coordinates", - "route": "/texture_coordinates", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "input_geode_object": { - "type": "string", - "minLength": 1 - }, - "filename": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "input_geode_object", - "filename" - ], - "additionalProperties": false - }, - "save_viewable_file": { - "$id": "opengeodeweb_back/save_viewable_file", - "route": "/save_viewable_file", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "input_geode_object": { - "type": "string", - "minLength": 1 - }, - "filename": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "input_geode_object", - "filename" - ], - "additionalProperties": false - }, - "polyhedron_attribute_names": { - "$id": "opengeodeweb_back/polyhedron_attribute_names", - "route": "/polyhedron_attribute_names", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "input_geode_object": { - "type": "string", - "minLength": 1 - }, - "filename": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "input_geode_object", - "filename" - ], - "additionalProperties": false - }, - "polygon_attribute_names": { - "$id": "opengeodeweb_back/polygon_attribute_names", - "route": "/polygon_attribute_names", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "input_geode_object": { - "type": "string", - "minLength": 1 - }, - "filename": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "input_geode_object", - "filename" - ], - "additionalProperties": false - }, - "ping": { - "$id": "opengeodeweb_back/ping", - "route": "/ping", - "methods": [ - "POST" - ], - "type": "object", - "properties": {}, - "required": [], - "additionalProperties": false - }, - "missing_files": { - "$id": "opengeodeweb_back/missing_files", - "route": "/missing_files", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "input_geode_object": { - "type": "string", - "minLength": 1 - }, - "filename": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "input_geode_object", - "filename" - ], - "additionalProperties": false - }, - "inspect_file": { - "$id": "opengeodeweb_back/inspect_file", - "route": "/inspect_file", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "filename": { - "type": "string", - "minLength": 1 - }, - "input_geode_object": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "filename", - "input_geode_object" - ], - "additionalProperties": false - }, - "geographic_coordinate_systems": { - "$id": "opengeodeweb_back/geographic_coordinate_systems", - "route": "/geographic_coordinate_systems", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "input_geode_object": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "input_geode_object" - ], - "additionalProperties": false - }, - "geode_objects_and_output_extensions": { - "$id": "opengeodeweb_back/geode_objects_and_output_extensions", - "route": "/geode_objects_and_output_extensions", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "input_geode_object": { - "type": "string", - "minLength": 1 - }, - "filename": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "input_geode_object", - "filename" - ], - "additionalProperties": false - }, - "create_point": { - "$id": "opengeodeweb_back/create_point", - "route": "/create_point", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "title": { - "type": "string", - "minLength": 1 - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - }, - "z": { - "type": "number" - } - }, - "required": [ - "title", - "x", - "y", - "z" - ], - "additionalProperties": false - }, - "allowed_objects": { - "$id": "opengeodeweb_back/allowed_objects", - "route": "/allowed_objects", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "filename": { - "type": "string", - "minLength": 1 - }, - "supported_feature": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "filename", - "supported_feature" - ], - "additionalProperties": false - }, - "allowed_files": { - "$id": "opengeodeweb_back/allowed_files", - "route": "/allowed_files", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "supported_feature": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "supported_feature" - ], - "additionalProperties": false - } - } -} \ No newline at end of file diff --git a/src/opengeodeweb_back/routes/create/blueprint_create.py b/src/opengeodeweb_back/routes/create/blueprint_create.py index ff05670d..d831c52e 100644 --- a/src/opengeodeweb_back/routes/create/blueprint_create.py +++ b/src/opengeodeweb_back/routes/create/blueprint_create.py @@ -79,59 +79,56 @@ def create_voi() -> flask.Response: print(f"create_voi : {flask.request=}", flush=True) utils_functions.validate_request(flask.request, schemas_dict["create_voi"]) params = schemas.CreateVoi.from_dict(flask.request.get_json()) - - # 1. Simuler la récupération des coordonnées (X, Y) de l'AOI - # ATTENTION : En l'absence de `utils_functions.get_data`, nous utilisons des - # points de simulation pour construire la VOI. L'ID de l'AOI (params.aoi_id) est ignoré. + + # Define the 4 vertices of the AOI (bottom face in the XY plane) aoi_vertices = [ - (0.0, 0.0), - (10.0, 0.0), - (10.0, 10.0), - (0.0, 10.0), + (params.min_x, params.min_y), + (params.max_x, params.min_y), + (params.max_x, params.max_y), + (params.min_x, params.max_y), ] - # 2. Créer le VOI (EdgedCurve3D) + # Create the EdgedCurve object and its builder edged_curve = geode_functions.geode_object_class("EdgedCurve3D").create() builder = geode_functions.create_builder("EdgedCurve3D", edged_curve) builder.set_name(params.name) - + + # Extract Z bounds z_min = params.z_min z_max = params.z_max - # 3. Créer les 8 vertices de la boîte (VOI) - # Indices 0-3 (face inférieure Z_min), Indices 4-7 (face supérieure Z_max) - - # Bottom face (Z_min) indices 0, 1, 2, 3 + # --- 1. Create the 8 vertices of the bounding box --- + + # Create the 4 bottom vertices (indices 0 to 3) for x, y in aoi_vertices: builder.create_point(opengeode.Point3D([x, y, z_min])) - - # Top face (Z_max) indices 4, 5, 6, 7 + + # Create the 4 top vertices (indices 4 to 7) for x, y in aoi_vertices: builder.create_point(opengeode.Point3D([x, y, z_max])) - # 4. Créer les 12 arêtes - - # Arêtes de la face inférieure: 0-1, 1-2, 2-3, 3-0 + # --- 2. Define and create the 12 edges of the bounding box --- + + # Edges of the bottom face (connecting vertices 0-1, 1-2, 2-3, 3-0) bottom_edges = [(i, (i + 1) % 4) for i in range(4)] - - # Arêtes de la face supérieure: 4-5, 5-6, 6-7, 7-4 + + # Edges of the top face (connecting vertices 4-5, 5-6, 6-7, 7-4) + # The (i + 4) and ((i + 1) % 4 + 4) ensure we use indices 4, 5, 6, 7 top_edges = [(i + 4, (i + 1) % 4 + 4) for i in range(4)] - - # Arêtes verticales: 0-4, 1-5, 2-6, 3-7 + + # Vertical edges (connecting bottom (0-3) to top (4-7) vertices: 0-4, 1-5, 2-6, 3-7) vertical_edges = [(i, i + 4) for i in range(4)] - + + # Combine all edges all_edges = bottom_edges + top_edges + vertical_edges - + + # Create the edges in the EdgedCurve for v1, v2 in all_edges: builder.create_edge_with_vertices(v1, v2) - # 5. Sauvegarder et obtenir les informations - # Utilise generate_native_viewable... car update_native_viewable... n'est pas disponible. - # L'ID optionnel (params.id) est donc ignoré, et une NOUVELLE entrée Data est créée. + # Save and get info result = utils_functions.generate_native_viewable_and_light_viewable_from_object( geode_object="EdgedCurve3D", data=edged_curve, ) - - # Retourne l'ID de la nouvelle entrée Data créée return flask.make_response(result, 200) \ No newline at end of file diff --git a/src/opengeodeweb_back/routes/create/schemas/create_voi.json b/src/opengeodeweb_back/routes/create/schemas/create_voi.json index bf3f3c50..29f7b5ff 100644 --- a/src/opengeodeweb_back/routes/create/schemas/create_voi.json +++ b/src/opengeodeweb_back/routes/create/schemas/create_voi.json @@ -11,23 +11,44 @@ }, "aoi_id": { "type": "string", - "description": "ID of the corresponding AOI from which to take X and Y coordinates" + "description": "ID of the corresponding AOI" + }, + "min_x": { + "type": "number", + "description": "Minimum X coordinate from AOI" + }, + "min_y": { + "type": "number", + "description": "Minimum Y coordinate from AOI" + }, + "max_x": { + "type": "number", + "description": "Maximum X coordinate from AOI" + }, + "max_y": { + "type": "number", + "description": "Maximum Y coordinate from AOI" }, "z_min": { "type": "number", - "description": "Minimum Z coordinate for the VOI" + "description": "Minimum Z coordinate" }, "z_max": { "type": "number", - "description": "Maximum Z coordinate for the VOI" + "description": "Maximum Z coordinate" }, "id": { - "type": "string" + "type": "string", + "description": "Optional ID for updating existing VOI" } }, "required": [ "name", "aoi_id", + "min_x", + "min_y", + "max_x", + "max_y", "z_min", "z_max" ], diff --git a/src/opengeodeweb_back/routes/create/schemas/create_voi.py b/src/opengeodeweb_back/routes/create/schemas/create_voi.py index 0bd492b0..4475a4b8 100644 --- a/src/opengeodeweb_back/routes/create/schemas/create_voi.py +++ b/src/opengeodeweb_back/routes/create/schemas/create_voi.py @@ -2,14 +2,25 @@ from dataclasses import dataclass from typing import Optional - @dataclass class CreateVoi(DataClassJsonMixin): name: str """Name of the VOI""" aoi_id: str - """ID of the corresponding AOI from which to take X and Y coordinates""" + """ID of the corresponding AOI""" + + min_x: float + """Minimum X coordinate from AOI""" + + min_y: float + """Minimum Y coordinate from AOI""" + + max_x: float + """Maximum X coordinate from AOI""" + + max_y: float + """Maximum Y coordinate from AOI""" z_min: float """Minimum Z coordinate for the VOI""" @@ -17,4 +28,4 @@ class CreateVoi(DataClassJsonMixin): z_max: float """Maximum Z coordinate for the VOI""" - id: Optional[str] = None \ No newline at end of file + id: Optional[str] = None From 84cbd39bf1d00668ecd160a7512b318133bfcdf5 Mon Sep 17 00:00:00 2001 From: SpliiT <106495600+SpliiT@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:49:29 +0000 Subject: [PATCH 3/8] Apply prepare changes --- opengeodeweb_back_schemas.json | 93 ++++++++++++------- requirements.txt | 1 - .../routes/create/blueprint_create.py | 2 +- .../routes/create/schemas/__init__.py | 2 +- .../routes/create/schemas/create_voi.py | 24 ++--- tests/test_create_routes.py | 10 +- 6 files changed, 77 insertions(+), 55 deletions(-) diff --git a/opengeodeweb_back_schemas.json b/opengeodeweb_back_schemas.json index ceb3e941..65b93582 100644 --- a/opengeodeweb_back_schemas.json +++ b/opengeodeweb_back_schemas.json @@ -1,6 +1,63 @@ { "opengeodeweb_back": { "create": { + "create_voi": { + "$id": "opengeodeweb_back/create/create_voi", + "route": "/create_voi", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the VOI" + }, + "aoi_id": { + "type": "string", + "description": "ID of the corresponding AOI" + }, + "min_x": { + "type": "number", + "description": "Minimum X coordinate from AOI" + }, + "min_y": { + "type": "number", + "description": "Minimum Y coordinate from AOI" + }, + "max_x": { + "type": "number", + "description": "Maximum X coordinate from AOI" + }, + "max_y": { + "type": "number", + "description": "Maximum Y coordinate from AOI" + }, + "z_min": { + "type": "number", + "description": "Minimum Z coordinate" + }, + "z_max": { + "type": "number", + "description": "Maximum Z coordinate" + }, + "id": { + "type": "string", + "description": "Optional ID for updating existing VOI" + } + }, + "required": [ + "name", + "aoi_id", + "min_x", + "min_y", + "max_x", + "max_y", + "z_min", + "z_max" + ], + "additionalProperties": false + }, "create_point": { "$id": "opengeodeweb_back/create/create_point", "route": "/create_point", @@ -77,42 +134,6 @@ "z" ], "additionalProperties": false - }, - "create_voi": { - "$id": "opengeodeweb_back/create/create_voi", - "route": "/create_voi", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the VOI" - }, - "aoi_id": { - "type": "string", - "description": "ID of the corresponding AOI from which to take X and Y coordinates" - }, - "z_min": { - "type": "number", - "description": "Minimum Z coordinate for the VOI" - }, - "z_max": { - "type": "number", - "description": "Maximum Z coordinate for the VOI" - }, - "id": { - "type": "string" - } - }, - "required": [ - "name", - "aoi_id", - "z_min", - "z_max" - ], - "additionalProperties": false } }, "models": { diff --git a/requirements.txt b/requirements.txt index 89b35fa5..4e85944d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,4 +60,3 @@ werkzeug==3.1.2 # flask # flask-cors -opengeodeweb-microservice==1.*,>=1.0.6rc1 diff --git a/src/opengeodeweb_back/routes/create/blueprint_create.py b/src/opengeodeweb_back/routes/create/blueprint_create.py index d831c52e..3a65330f 100644 --- a/src/opengeodeweb_back/routes/create/blueprint_create.py +++ b/src/opengeodeweb_back/routes/create/blueprint_create.py @@ -131,4 +131,4 @@ def create_voi() -> flask.Response: geode_object="EdgedCurve3D", data=edged_curve, ) - return flask.make_response(result, 200) \ No newline at end of file + return flask.make_response(result, 200) diff --git a/src/opengeodeweb_back/routes/create/schemas/__init__.py b/src/opengeodeweb_back/routes/create/schemas/__init__.py index 6a710590..3d3f9b73 100644 --- a/src/opengeodeweb_back/routes/create/schemas/__init__.py +++ b/src/opengeodeweb_back/routes/create/schemas/__init__.py @@ -1,3 +1,3 @@ +from .create_voi import * from .create_point import * from .create_aoi import * -from .create_voi import * diff --git a/src/opengeodeweb_back/routes/create/schemas/create_voi.py b/src/opengeodeweb_back/routes/create/schemas/create_voi.py index 4475a4b8..25a046f6 100644 --- a/src/opengeodeweb_back/routes/create/schemas/create_voi.py +++ b/src/opengeodeweb_back/routes/create/schemas/create_voi.py @@ -2,30 +2,32 @@ from dataclasses import dataclass from typing import Optional + @dataclass class CreateVoi(DataClassJsonMixin): - name: str - """Name of the VOI""" - aoi_id: str """ID of the corresponding AOI""" + max_x: float + """Maximum X coordinate from AOI""" + + max_y: float + """Maximum Y coordinate from AOI""" + min_x: float """Minimum X coordinate from AOI""" min_y: float """Minimum Y coordinate from AOI""" - max_x: float - """Maximum X coordinate from AOI""" + name: str + """Name of the VOI""" - max_y: float - """Maximum Y coordinate from AOI""" + z_max: float + """Maximum Z coordinate""" z_min: float - """Minimum Z coordinate for the VOI""" - - z_max: float - """Maximum Z coordinate for the VOI""" + """Minimum Z coordinate""" id: Optional[str] = None + """Optional ID for updating existing VOI""" diff --git a/tests/test_create_routes.py b/tests/test_create_routes.py index 3e823ede..43cfe0c8 100644 --- a/tests/test_create_routes.py +++ b/tests/test_create_routes.py @@ -29,15 +29,16 @@ def aoi_data() -> Dict[str, Any]: "z": 0.0, } + @pytest.fixture def voi_data() -> Dict[str, Any]: """Fixture for Volume of Interest (VOI) test data.""" return { "name": "test_voi", - "aoi_id": str(uuid.uuid4()), + "aoi_id": str(uuid.uuid4()), "z_min": -50.0, "z_max": 100.0, - "id": str(uuid.uuid4()), + "id": str(uuid.uuid4()), } @@ -113,9 +114,8 @@ def test_create_voi(client: FlaskClient, voi_data: Dict[str, Any]) -> None: voi_data_required_only = voi_data.copy() del voi_data_required_only["id"] - - test_utils.test_route_wrong_params(client, route, lambda: voi_data_required_only.copy()) # type: ignore + test_utils.test_route_wrong_params(client, route, lambda: voi_data_required_only.copy()) # type: ignore def test_create_point_with_invalid_data(client: FlaskClient) -> None: @@ -183,7 +183,7 @@ def test_create_voi_with_invalid_data( invalid_data = {**voi_data, "z_max": "not_a_number"} response = client.post(route, json=invalid_data) assert response.status_code == 400 - + # Test with invalid aoi_id format (e.g., not a string/uuid) invalid_data = {**voi_data, "aoi_id": 12345} response = client.post(route, json=invalid_data) From e77805a000449759909882ed82c92967c347972e Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 4 Nov 2025 10:04:24 +0100 Subject: [PATCH 4/8] changes --- opengeodeweb_back_schemas.json | 12 +++--- .../routes/create/blueprint_create.py | 37 ++++++++++--------- .../routes/create/schemas/create_aoi.json | 3 +- .../routes/create/schemas/create_voi.json | 20 ---------- .../routes/create/schemas/create_voi.py | 12 ------ 5 files changed, 26 insertions(+), 58 deletions(-) diff --git a/opengeodeweb_back_schemas.json b/opengeodeweb_back_schemas.json index ceb3e941..51853c9c 100644 --- a/opengeodeweb_back_schemas.json +++ b/opengeodeweb_back_schemas.json @@ -61,8 +61,7 @@ ], "additionalProperties": false }, - "minItems": 4, - "maxItems": 4 + "minItems": 3 }, "z": { "type": "number" @@ -92,18 +91,19 @@ }, "aoi_id": { "type": "string", - "description": "ID of the corresponding AOI from which to take X and Y coordinates" + "description": "ID of the corresponding AOI" }, "z_min": { "type": "number", - "description": "Minimum Z coordinate for the VOI" + "description": "Minimum Z coordinate" }, "z_max": { "type": "number", - "description": "Maximum Z coordinate for the VOI" + "description": "Maximum Z coordinate" }, "id": { - "type": "string" + "type": "string", + "description": "Optional ID for updating existing VOI" } }, "required": [ diff --git a/src/opengeodeweb_back/routes/create/blueprint_create.py b/src/opengeodeweb_back/routes/create/blueprint_create.py index d831c52e..bea5010a 100644 --- a/src/opengeodeweb_back/routes/create/blueprint_create.py +++ b/src/opengeodeweb_back/routes/create/blueprint_create.py @@ -80,55 +80,56 @@ def create_voi() -> flask.Response: utils_functions.validate_request(flask.request, schemas_dict["create_voi"]) params = schemas.CreateVoi.from_dict(flask.request.get_json()) - # Define the 4 vertices of the AOI (bottom face in the XY plane) + aoi_data = geode_functions.get_data_info(params.aoi_id) + if not aoi_data: + flask.abort(404, f"AOI with id {params.aoi_id} not found") + + edged_curve_aoi = geode_functions.load_data(params.aoi_id) + if not isinstance(edged_curve_aoi, opengeode.EdgedCurve3D): + flask.abort(400, "Referenced object is not an EdgedCurve3D (AOI)") + + bbox_aoi = edged_curve_aoi.bounding_box() + min_x = bbox_aoi.min().value(0) + min_y = bbox_aoi.min().value(1) + max_x = bbox_aoi.max().value(0) + max_y = bbox_aoi.max().value(1) + aoi_vertices = [ - (params.min_x, params.min_y), - (params.max_x, params.min_y), - (params.max_x, params.max_y), - (params.min_x, params.max_y), + (min_x, min_y), + (max_x, min_y), + (max_x, max_y), + (min_x, max_y), ] - # Create the EdgedCurve object and its builder edged_curve = geode_functions.geode_object_class("EdgedCurve3D").create() builder = geode_functions.create_builder("EdgedCurve3D", edged_curve) builder.set_name(params.name) - # Extract Z bounds z_min = params.z_min z_max = params.z_max - # --- 1. Create the 8 vertices of the bounding box --- - # Create the 4 bottom vertices (indices 0 to 3) for x, y in aoi_vertices: builder.create_point(opengeode.Point3D([x, y, z_min])) - # Create the 4 top vertices (indices 4 to 7) for x, y in aoi_vertices: builder.create_point(opengeode.Point3D([x, y, z_max])) - # --- 2. Define and create the 12 edges of the bounding box --- - # Edges of the bottom face (connecting vertices 0-1, 1-2, 2-3, 3-0) bottom_edges = [(i, (i + 1) % 4) for i in range(4)] - # Edges of the top face (connecting vertices 4-5, 5-6, 6-7, 7-4) - # The (i + 4) and ((i + 1) % 4 + 4) ensure we use indices 4, 5, 6, 7 top_edges = [(i + 4, (i + 1) % 4 + 4) for i in range(4)] - # Vertical edges (connecting bottom (0-3) to top (4-7) vertices: 0-4, 1-5, 2-6, 3-7) vertical_edges = [(i, i + 4) for i in range(4)] - # Combine all edges all_edges = bottom_edges + top_edges + vertical_edges - # Create the edges in the EdgedCurve for v1, v2 in all_edges: builder.create_edge_with_vertices(v1, v2) - # Save and get info result = utils_functions.generate_native_viewable_and_light_viewable_from_object( geode_object="EdgedCurve3D", data=edged_curve, ) + result["aoi_id"] = params.aoi_id return flask.make_response(result, 200) \ No newline at end of file diff --git a/src/opengeodeweb_back/routes/create/schemas/create_aoi.json b/src/opengeodeweb_back/routes/create/schemas/create_aoi.json index 0dc7a3f4..c9321491 100644 --- a/src/opengeodeweb_back/routes/create/schemas/create_aoi.json +++ b/src/opengeodeweb_back/routes/create/schemas/create_aoi.json @@ -27,8 +27,7 @@ ], "additionalProperties": false }, - "minItems": 4, - "maxItems": 4 + "minItems": 3 }, "z": { "type": "number" diff --git a/src/opengeodeweb_back/routes/create/schemas/create_voi.json b/src/opengeodeweb_back/routes/create/schemas/create_voi.json index 29f7b5ff..1b3d630d 100644 --- a/src/opengeodeweb_back/routes/create/schemas/create_voi.json +++ b/src/opengeodeweb_back/routes/create/schemas/create_voi.json @@ -13,22 +13,6 @@ "type": "string", "description": "ID of the corresponding AOI" }, - "min_x": { - "type": "number", - "description": "Minimum X coordinate from AOI" - }, - "min_y": { - "type": "number", - "description": "Minimum Y coordinate from AOI" - }, - "max_x": { - "type": "number", - "description": "Maximum X coordinate from AOI" - }, - "max_y": { - "type": "number", - "description": "Maximum Y coordinate from AOI" - }, "z_min": { "type": "number", "description": "Minimum Z coordinate" @@ -45,10 +29,6 @@ "required": [ "name", "aoi_id", - "min_x", - "min_y", - "max_x", - "max_y", "z_min", "z_max" ], diff --git a/src/opengeodeweb_back/routes/create/schemas/create_voi.py b/src/opengeodeweb_back/routes/create/schemas/create_voi.py index 4475a4b8..d8d5ebf9 100644 --- a/src/opengeodeweb_back/routes/create/schemas/create_voi.py +++ b/src/opengeodeweb_back/routes/create/schemas/create_voi.py @@ -10,18 +10,6 @@ class CreateVoi(DataClassJsonMixin): aoi_id: str """ID of the corresponding AOI""" - min_x: float - """Minimum X coordinate from AOI""" - - min_y: float - """Minimum Y coordinate from AOI""" - - max_x: float - """Maximum X coordinate from AOI""" - - max_y: float - """Maximum Y coordinate from AOI""" - z_min: float """Minimum Z coordinate for the VOI""" From 1c4f4ee5cb9e03ce97a2468f861ec62a0ac6a48b Mon Sep 17 00:00:00 2001 From: SpliiT Date: Tue, 4 Nov 2025 14:29:25 +0100 Subject: [PATCH 5/8] last changes back --- .../routes/create/blueprint_create.py | 62 ++++++------------- .../routes/create/schemas/create_voi.py | 12 ---- 2 files changed, 18 insertions(+), 56 deletions(-) diff --git a/src/opengeodeweb_back/routes/create/blueprint_create.py b/src/opengeodeweb_back/routes/create/blueprint_create.py index bea5010a..d160951e 100644 --- a/src/opengeodeweb_back/routes/create/blueprint_create.py +++ b/src/opengeodeweb_back/routes/create/blueprint_create.py @@ -20,7 +20,6 @@ ) def create_point() -> flask.Response: """Endpoint to create a single point in 3D space.""" - print(f"create_point : {flask.request=}", flush=True) utils_functions.validate_request(flask.request, schemas_dict["create_point"]) params = schemas.CreatePoint.from_dict(flask.request.get_json()) @@ -43,7 +42,6 @@ def create_point() -> flask.Response: ) def create_aoi() -> flask.Response: """Endpoint to create an Area of Interest (AOI) as an EdgedCurve3D.""" - print(f"create_aoi : {flask.request=}", flush=True) utils_functions.validate_request(flask.request, schemas_dict["create_aoi"]) params = schemas.CreateAoi.from_dict(flask.request.get_json()) @@ -54,7 +52,6 @@ def create_aoi() -> flask.Response: # Create vertices first for point in params.points: - # pp = opengeode.Point3D([point.x, point.y, params.z]) builder.create_point(opengeode.Point3D([point.x, point.y, params.z])) # Create edges between consecutive vertices and close the loop @@ -70,13 +67,12 @@ def create_aoi() -> flask.Response: ) return flask.make_response(result, 200) - @routes.route( - schemas_dict["create_voi"]["route"], methods=schemas_dict["create_voi"]["methods"] + schemas_dict["create_voi"]["route"], + methods=schemas_dict["create_voi"]["methods"], ) def create_voi() -> flask.Response: - """Endpoint to create a Volume of Interest (VOI) as an EdgedCurve3D (a bounding box).""" - print(f"create_voi : {flask.request=}", flush=True) + """Endpoint to create a Volume of Interest (VOI) as an EdgedCurve3D (a bounding box/prism).""" utils_functions.validate_request(flask.request, schemas_dict["create_voi"]) params = schemas.CreateVoi.from_dict(flask.request.get_json()) @@ -84,52 +80,30 @@ def create_voi() -> flask.Response: if not aoi_data: flask.abort(404, f"AOI with id {params.aoi_id} not found") - edged_curve_aoi = geode_functions.load_data(params.aoi_id) - if not isinstance(edged_curve_aoi, opengeode.EdgedCurve3D): - flask.abort(400, "Referenced object is not an EdgedCurve3D (AOI)") - - bbox_aoi = edged_curve_aoi.bounding_box() - min_x = bbox_aoi.min().value(0) - min_y = bbox_aoi.min().value(1) - max_x = bbox_aoi.max().value(0) - max_y = bbox_aoi.max().value(1) - - aoi_vertices = [ - (min_x, min_y), - (max_x, min_y), - (max_x, max_y), - (min_x, max_y), - ] + aoi_object = geode_functions.load_data(params.aoi_id) + + nb_points = aoi_object.nb_vertices() edged_curve = geode_functions.geode_object_class("EdgedCurve3D").create() builder = geode_functions.create_builder("EdgedCurve3D", edged_curve) builder.set_name(params.name) - z_min = params.z_min - z_max = params.z_max - - - for x, y in aoi_vertices: - builder.create_point(opengeode.Point3D([x, y, z_min])) - - for x, y in aoi_vertices: - builder.create_point(opengeode.Point3D([x, y, z_max])) - - - bottom_edges = [(i, (i + 1) % 4) for i in range(4)] - - top_edges = [(i + 4, (i + 1) % 4 + 4) for i in range(4)] - - vertical_edges = [(i, i + 4) for i in range(4)] - - all_edges = bottom_edges + top_edges + vertical_edges + for point_id in range(nb_points): + aoi_point= aoi_object.point(point_id) + builder.create_point(opengeode.Point3D([aoi_point.value(0), aoi_point.value(1), params.z_min])) + + for point_id in range(nb_points): + aoi_point= aoi_object.point(point_id) + builder.create_point(opengeode.Point3D([aoi_point.value(0), aoi_point.value(1), params.z_max])) - for v1, v2 in all_edges: - builder.create_edge_with_vertices(v1, v2) + for point_id in range(nb_points): + next_point = (point_id + 1) % nb_points + builder.create_edge_with_vertices(point_id, next_point) + builder.create_edge_with_vertices(point_id+nb_points, next_point+nb_points) + builder.create_edge_with_vertices(point_id, point_id+nb_points) result = utils_functions.generate_native_viewable_and_light_viewable_from_object( geode_object="EdgedCurve3D", data=edged_curve, ) - result["aoi_id"] = params.aoi_id return flask.make_response(result, 200) \ No newline at end of file diff --git a/src/opengeodeweb_back/routes/create/schemas/create_voi.py b/src/opengeodeweb_back/routes/create/schemas/create_voi.py index 25a046f6..f1a5b23b 100644 --- a/src/opengeodeweb_back/routes/create/schemas/create_voi.py +++ b/src/opengeodeweb_back/routes/create/schemas/create_voi.py @@ -8,18 +8,6 @@ class CreateVoi(DataClassJsonMixin): aoi_id: str """ID of the corresponding AOI""" - max_x: float - """Maximum X coordinate from AOI""" - - max_y: float - """Maximum Y coordinate from AOI""" - - min_x: float - """Minimum X coordinate from AOI""" - - min_y: float - """Minimum Y coordinate from AOI""" - name: str """Name of the VOI""" From fe36074c9ce1a4e8eb7e2605085199322f13489a Mon Sep 17 00:00:00 2001 From: SpliiT <106495600+SpliiT@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:58:45 +0000 Subject: [PATCH 6/8] Apply prepare changes --- requirements.txt | 1 - .../routes/create/blueprint_create.py | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index f936c058..4e85944d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,4 +60,3 @@ werkzeug==3.1.2 # flask # flask-cors -opengeodeweb-microservice==1.*,>=1.0.7rc1 diff --git a/src/opengeodeweb_back/routes/create/blueprint_create.py b/src/opengeodeweb_back/routes/create/blueprint_create.py index d160951e..472038a9 100644 --- a/src/opengeodeweb_back/routes/create/blueprint_create.py +++ b/src/opengeodeweb_back/routes/create/blueprint_create.py @@ -67,6 +67,7 @@ def create_aoi() -> flask.Response: ) return flask.make_response(result, 200) + @routes.route( schemas_dict["create_voi"]["route"], methods=schemas_dict["create_voi"]["methods"], @@ -81,7 +82,7 @@ def create_voi() -> flask.Response: flask.abort(404, f"AOI with id {params.aoi_id} not found") aoi_object = geode_functions.load_data(params.aoi_id) - + nb_points = aoi_object.nb_vertices() edged_curve = geode_functions.geode_object_class("EdgedCurve3D").create() @@ -89,21 +90,25 @@ def create_voi() -> flask.Response: builder.set_name(params.name) for point_id in range(nb_points): - aoi_point= aoi_object.point(point_id) - builder.create_point(opengeode.Point3D([aoi_point.value(0), aoi_point.value(1), params.z_min])) - + aoi_point = aoi_object.point(point_id) + builder.create_point( + opengeode.Point3D([aoi_point.value(0), aoi_point.value(1), params.z_min]) + ) + for point_id in range(nb_points): - aoi_point= aoi_object.point(point_id) - builder.create_point(opengeode.Point3D([aoi_point.value(0), aoi_point.value(1), params.z_max])) + aoi_point = aoi_object.point(point_id) + builder.create_point( + opengeode.Point3D([aoi_point.value(0), aoi_point.value(1), params.z_max]) + ) for point_id in range(nb_points): next_point = (point_id + 1) % nb_points builder.create_edge_with_vertices(point_id, next_point) - builder.create_edge_with_vertices(point_id+nb_points, next_point+nb_points) - builder.create_edge_with_vertices(point_id, point_id+nb_points) + builder.create_edge_with_vertices(point_id + nb_points, next_point + nb_points) + builder.create_edge_with_vertices(point_id, point_id + nb_points) result = utils_functions.generate_native_viewable_and_light_viewable_from_object( geode_object="EdgedCurve3D", data=edged_curve, ) - return flask.make_response(result, 200) \ No newline at end of file + return flask.make_response(result, 200) From 1a119cedc38c0016faef00d2bd21e033214f3f4a Mon Sep 17 00:00:00 2001 From: SpliiT Date: Wed, 5 Nov 2025 11:40:05 +0100 Subject: [PATCH 7/8] green lights --- pyproject.toml | 2 +- .../routes/create/blueprint_create.py | 6 +- tests/test_create_routes.py | 168 +++--------------- 3 files changed, 24 insertions(+), 152 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 95c0298b..c1add323 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,4 +33,4 @@ where = ["src"] "opengeodeweb_back.routes.schemas" = ["*.json"] "opengeodeweb_back.routes.models.schemas" = ["*.json"] "opengeodeweb_back.routes.create.schemas" = ["*.json"] -opengeodeweb_microservice = ["py.typed"] +"opengeodeweb_back" = ["py.typed"] diff --git a/src/opengeodeweb_back/routes/create/blueprint_create.py b/src/opengeodeweb_back/routes/create/blueprint_create.py index 472038a9..2b780d45 100644 --- a/src/opengeodeweb_back/routes/create/blueprint_create.py +++ b/src/opengeodeweb_back/routes/create/blueprint_create.py @@ -13,7 +13,6 @@ routes = flask.Blueprint("create", __name__, url_prefix="/create") schemas_dict = get_schemas_dict(os.path.join(os.path.dirname(__file__), "schemas")) - @routes.route( schemas_dict["create_point"]["route"], methods=schemas_dict["create_point"]["methods"], @@ -38,7 +37,8 @@ def create_point() -> flask.Response: @routes.route( - schemas_dict["create_aoi"]["route"], methods=schemas_dict["create_aoi"]["methods"] + schemas_dict["create_aoi"]["route"], + methods=schemas_dict["create_aoi"]["methods"] ) def create_aoi() -> flask.Response: """Endpoint to create an Area of Interest (AOI) as an EdgedCurve3D.""" @@ -70,7 +70,7 @@ def create_aoi() -> flask.Response: @routes.route( schemas_dict["create_voi"]["route"], - methods=schemas_dict["create_voi"]["methods"], + methods=schemas_dict["create_voi"]["methods"] ) def create_voi() -> flask.Response: """Endpoint to create a Volume of Interest (VOI) as an EdgedCurve3D (a bounding box/prism).""" diff --git a/tests/test_create_routes.py b/tests/test_create_routes.py index 3e9eff72..ab04b744 100644 --- a/tests/test_create_routes.py +++ b/tests/test_create_routes.py @@ -91,33 +91,26 @@ def test_create_aoi(client: FlaskClient, aoi_data: Dict[str, Any]) -> None: # Test with missing parameters test_utils.test_route_wrong_params(client, route, lambda: aoi_data.copy()) # type: ignore - -def test_create_voi(client: FlaskClient, voi_data: Dict[str, Any]) -> None: +def test_create_voi(client: FlaskClient, aoi_data: Dict[str, Any], voi_data: Dict[str, Any]) -> None: """Test the creation of a VOI with valid data (including optional id).""" - route: str = "/opengeodeweb_back/create/create_voi" + aoi_route = "/opengeodeweb_back/create/create_aoi" + aoi_response = client.post(aoi_route, json=aoi_data) + assert aoi_response.status_code == 200 + aoi_id = aoi_response.json["id"] + + voi_data["aoi_id"] = aoi_id - response = client.post(route, json=voi_data) + voi_route = "/opengeodeweb_back/create/create_voi" + response = client.post(voi_route, json=voi_data) assert response.status_code == 200 - response_data: Any = response.json + response_data = response.json assert "id" in response_data assert "name" in response_data assert response_data["name"] == voi_data["name"] assert response_data["object_type"] == "mesh" assert response_data["geode_object"] == "EdgedCurve3D" - voi_data_no_id = voi_data.copy() - del voi_data_no_id["id"] - response = client.post(route, json=voi_data_no_id) - assert response.status_code == 200 - assert response.json["name"] == voi_data["name"] - - voi_data_required_only = voi_data.copy() - del voi_data_required_only["id"] - - test_utils.test_route_wrong_params(client, route, lambda: voi_data_required_only.copy()) # type: ignore - - def test_create_point_with_invalid_data(client: FlaskClient) -> None: """Test the point creation endpoint with invalid data.""" route: str = "/opengeodeweb_back/create/create_point" @@ -144,7 +137,6 @@ def test_create_aoi_with_invalid_data( """Test the AOI creation endpoint with invalid data.""" route: str = "/opengeodeweb_back/create/create_aoi" - # Test with invalid points invalid_data: Dict[str, Any] = { **aoi_data, "points": [ @@ -157,154 +149,34 @@ def test_create_aoi_with_invalid_data( response = client.post(route, json=invalid_data) assert response.status_code == 400 - # Test with too few points invalid_data = {**aoi_data, "points": [{"x": 0.0, "y": 0.0}, {"x": 1.0, "y": 0.0}]} response = client.post(route, json=invalid_data) assert response.status_code == 400 - # Test with invalid z value invalid_data = {**aoi_data, "z": "not_a_number"} response = client.post(route, json=invalid_data) assert response.status_code == 400 def test_create_voi_with_invalid_data( - client: FlaskClient, voi_data: Dict[str, Any] + client: FlaskClient, aoi_data: Dict[str, Any], voi_data: Dict[str, Any] ) -> None: """Test the VOI creation endpoint with invalid data.""" - route: str = "/opengeodeweb_back/create/create_voi" + aoi_route = "/opengeodeweb_back/create/create_aoi" + aoi_response = client.post(aoi_route, json=aoi_data) + assert aoi_response.status_code == 200 + aoi_id = aoi_response.json["id"] + + route = "/opengeodeweb_back/create/create_aoi" - # Test with non-numeric z_min - invalid_data: Dict[str, Any] = {**voi_data, "z_min": "not_a_number"} + invalid_data = {**voi_data, "aoi_id": aoi_id, "z_min": "not_a_number"} response = client.post(route, json=invalid_data) assert response.status_code == 400 - # Test with non-numeric z_max - invalid_data = {**voi_data, "z_max": "not_a_number"} + invalid_data = {**voi_data, "aoi_id": aoi_id, "z_max": "not_a_number"} response = client.post(route, json=invalid_data) assert response.status_code == 400 - # Test with invalid aoi_id format (e.g., not a string/uuid) invalid_data = {**voi_data, "aoi_id": 12345} response = client.post(route, json=invalid_data) - assert response.status_code == 400 - - -def test_create_point_file_generation( - client: FlaskClient, point_data: Dict[str, Any] -) -> None: - """Test that the point creation generates the correct files.""" - route: str = "/opengeodeweb_back/create/create_point" - - # Make the request - response = client.post(route, json=point_data) - assert response.status_code == 200 - response_data: Any = response.json - - # Get the data folder path for this specific ID - DATA_FOLDER_PATH: str = client.application.config["DATA_FOLDER_PATH"] - data_id: str = response_data["id"] - data_folder: str = os.path.join(DATA_FOLDER_PATH, data_id) - - # Check that the data folder exists - assert os.path.exists(data_folder) - assert os.path.isdir(data_folder) - - # Check native file exists - native_file_path: str = os.path.join(data_folder, response_data["native_file_name"]) - assert os.path.exists(native_file_path) - - # Check viewable file exists - viewable_file_path: str = os.path.join( - data_folder, response_data["viewable_file_name"] - ) - assert os.path.exists(viewable_file_path) - - # Check light viewable file exists if present - if "binary_light_viewable" in response_data: - light_viewable_file_path: str = os.path.join(data_folder, "light_viewable.vtp") - assert os.path.exists(light_viewable_file_path) - - # Verify file extensions - assert response_data["native_file_name"].endswith(".og_pts3d") - assert response_data["viewable_file_name"].endswith(".vtp") - - -def test_create_aoi_file_generation( - client: FlaskClient, aoi_data: Dict[str, Any] -) -> None: - """Test that the AOI creation generates the correct files.""" - route: str = "/opengeodeweb_back/create/create_aoi" - - # Make the request - response = client.post(route, json=aoi_data) - assert response.status_code == 200 - response_data: Any = response.json - - # Get the data folder path for this specific ID - DATA_FOLDER_PATH: str = client.application.config["DATA_FOLDER_PATH"] - data_id: str = response_data["id"] - data_folder: str = os.path.join(DATA_FOLDER_PATH, data_id) - - # Check that the data folder exists - assert os.path.exists(data_folder) - assert os.path.isdir(data_folder) - - # Check native file exists - native_file_path: str = os.path.join(data_folder, response_data["native_file_name"]) - assert os.path.exists(native_file_path) - - # Check viewable file exists - viewable_file_path: str = os.path.join( - data_folder, response_data["viewable_file_name"] - ) - assert os.path.exists(viewable_file_path) - - # Check light viewable file exists if present - if "binary_light_viewable" in response_data: - light_viewable_file_path: str = os.path.join(data_folder, "light_viewable.vtp") - assert os.path.exists(light_viewable_file_path) - - # Verify file extensions - assert response_data["native_file_name"].endswith(".og_edc3d") - assert response_data["viewable_file_name"].endswith(".vtp") - - -def test_create_voi_file_generation( - client: FlaskClient, voi_data: Dict[str, Any] -) -> None: - """Test that the VOI creation generates the correct files.""" - route: str = "/opengeodeweb_back/create/create_voi" - - # Make the request - response = client.post(route, json=voi_data) - assert response.status_code == 200 - response_data: Any = response.json - - # Get the data folder path for this specific ID - DATA_FOLDER_PATH: str = client.application.config["DATA_FOLDER_PATH"] - data_id: str = response_data["id"] - data_folder: str = os.path.join(DATA_FOLDER_PATH, data_id) - - # Check that the data folder exists - assert os.path.exists(data_folder) - assert os.path.isdir(data_folder) - - # Check native file exists - native_file_path: str = os.path.join(data_folder, response_data["native_file_name"]) - assert os.path.exists(native_file_path) - - # Check viewable file exists - viewable_file_path: str = os.path.join( - data_folder, response_data["viewable_file_name"] - ) - assert os.path.exists(viewable_file_path) - - # Check light viewable file exists if present - if "binary_light_viewable" in response_data: - light_viewable_file_path: str = os.path.join(data_folder, "light_viewable.vtp") - assert os.path.exists(light_viewable_file_path) - - # Verify file extensions (VOI uses EdgedCurve3D like AOI) - assert response_data["native_file_name"].endswith(".og_edc3d") - assert response_data["viewable_file_name"].endswith(".vtp") + assert response.status_code == 400 \ No newline at end of file From 81f203fee9030e358058a82bfdd812d39d708a55 Mon Sep 17 00:00:00 2001 From: SpliiT <106495600+SpliiT@users.noreply.github.com> Date: Wed, 5 Nov 2025 10:40:53 +0000 Subject: [PATCH 8/8] Apply prepare changes --- .../routes/create/blueprint_create.py | 7 +++---- tests/test_create_routes.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/opengeodeweb_back/routes/create/blueprint_create.py b/src/opengeodeweb_back/routes/create/blueprint_create.py index 2b780d45..10766664 100644 --- a/src/opengeodeweb_back/routes/create/blueprint_create.py +++ b/src/opengeodeweb_back/routes/create/blueprint_create.py @@ -13,6 +13,7 @@ routes = flask.Blueprint("create", __name__, url_prefix="/create") schemas_dict = get_schemas_dict(os.path.join(os.path.dirname(__file__), "schemas")) + @routes.route( schemas_dict["create_point"]["route"], methods=schemas_dict["create_point"]["methods"], @@ -37,8 +38,7 @@ def create_point() -> flask.Response: @routes.route( - schemas_dict["create_aoi"]["route"], - methods=schemas_dict["create_aoi"]["methods"] + schemas_dict["create_aoi"]["route"], methods=schemas_dict["create_aoi"]["methods"] ) def create_aoi() -> flask.Response: """Endpoint to create an Area of Interest (AOI) as an EdgedCurve3D.""" @@ -69,8 +69,7 @@ def create_aoi() -> flask.Response: @routes.route( - schemas_dict["create_voi"]["route"], - methods=schemas_dict["create_voi"]["methods"] + schemas_dict["create_voi"]["route"], methods=schemas_dict["create_voi"]["methods"] ) def create_voi() -> flask.Response: """Endpoint to create a Volume of Interest (VOI) as an EdgedCurve3D (a bounding box/prism).""" diff --git a/tests/test_create_routes.py b/tests/test_create_routes.py index ab04b744..974e996a 100644 --- a/tests/test_create_routes.py +++ b/tests/test_create_routes.py @@ -91,9 +91,12 @@ def test_create_aoi(client: FlaskClient, aoi_data: Dict[str, Any]) -> None: # Test with missing parameters test_utils.test_route_wrong_params(client, route, lambda: aoi_data.copy()) # type: ignore -def test_create_voi(client: FlaskClient, aoi_data: Dict[str, Any], voi_data: Dict[str, Any]) -> None: + +def test_create_voi( + client: FlaskClient, aoi_data: Dict[str, Any], voi_data: Dict[str, Any] +) -> None: """Test the creation of a VOI with valid data (including optional id).""" - aoi_route = "/opengeodeweb_back/create/create_aoi" + aoi_route = "/opengeodeweb_back/create/create_aoi" aoi_response = client.post(aoi_route, json=aoi_data) assert aoi_response.status_code == 200 aoi_id = aoi_response.json["id"] @@ -111,6 +114,7 @@ def test_create_voi(client: FlaskClient, aoi_data: Dict[str, Any], voi_data: Dic assert response_data["object_type"] == "mesh" assert response_data["geode_object"] == "EdgedCurve3D" + def test_create_point_with_invalid_data(client: FlaskClient) -> None: """Test the point creation endpoint with invalid data.""" route: str = "/opengeodeweb_back/create/create_point" @@ -167,7 +171,7 @@ def test_create_voi_with_invalid_data( assert aoi_response.status_code == 200 aoi_id = aoi_response.json["id"] - route = "/opengeodeweb_back/create/create_aoi" + route = "/opengeodeweb_back/create/create_aoi" invalid_data = {**voi_data, "aoi_id": aoi_id, "z_min": "not_a_number"} response = client.post(route, json=invalid_data) @@ -179,4 +183,4 @@ def test_create_voi_with_invalid_data( invalid_data = {**voi_data, "aoi_id": 12345} response = client.post(route, json=invalid_data) - assert response.status_code == 400 \ No newline at end of file + assert response.status_code == 400