From b40e8e30711c2b29f2ea1e4ede1a05aca9ffc631 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 22 Oct 2026 14:43:02 +0200 Subject: [PATCH 01/10] share check_upload and check_save_viewalble [skip ci] --- src/opengeodeweb_back/test_utils.py | 41 +++++++++++++++++++ tests/test_routes.py | 62 +++++------------------------ 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/src/opengeodeweb_back/test_utils.py b/src/opengeodeweb_back/test_utils.py index c6df3cb8..73c19a2b 100644 --- a/src/opengeodeweb_back/test_utils.py +++ b/src/opengeodeweb_back/test_utils.py @@ -2,14 +2,17 @@ # Standard library imports from http import HTTPStatus +from pathlib import Path from typing import TYPE_CHECKING, Any # Third party imports +from werkzeug.datastructures import FileStorage if TYPE_CHECKING: from collections.abc import Callable from flask.testing import FlaskClient + from werkzeug.test import TestResponse # Local application imports @@ -73,3 +76,41 @@ def get_json() -> tuple[JsonData, Any]: ) elif isinstance(data, list) and data: _check_wrong_params_at(client, route, get_full_data, [*path, 0]) + + +def check_upload_file(client: FlaskClient, filename: str) -> None: + # Test data lives in the "data" folder of the tests' project folder + file = Path(client.application.config["PROJECT_FOLDER_PATH"]) / "data" / filename + response = client.put( + "/opengeodeweb_back/upload_file", + data={"file": FileStorage(file.open("rb"))}, + ) + _check(response.status_code == HTTPStatus.CREATED, f"Upload of {filename} failed") + + +def check_save_viewable_file( + client: FlaskClient, + geode_object_type: str, + filename: str, +) -> TestResponse: + check_upload_file(client, filename) + route = "/opengeodeweb_back/save_viewable_file" + + def get_full_data() -> JsonData: + return { + "geode_object_type": geode_object_type, + "filename": filename, + } + + response = client.post(route, json=get_full_data()) + _check(response.status_code == HTTPStatus.OK, f"Saving {filename} failed") + json = response.get_json() + _check(type(json["native_file"]) is str, "native_file is not a string") + _check(type(json["viewable_file"]) is str, "viewable_file is not a string") + _check(type(json.get("id")) is str, "id is not a string") + _check(json["viewer_type"] in ["model", "mesh"], "viewer_type is not model or mesh") + _check(type(json["binary_light_viewable"]) is str, "binary_light_viewable is not a string") + + # Test all params + test_route_wrong_params(client, route, get_full_data) + return response diff --git a/tests/test_routes.py b/tests/test_routes.py index 594a9343..6510cc6c 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -16,7 +16,6 @@ # Local application imports from opengeodeweb_microservice.database.data import Data -from werkzeug.datastructures import FileStorage from opengeodeweb_back import geode_functions, test_utils from opengeodeweb_back.geode_objects.geode_edged_curve3d import ( @@ -84,16 +83,7 @@ def get_full_data() -> test_utils.JsonData: def test_upload_file(client: FlaskClient) -> None: - check_upload_file(client, "test.og_brep") - - -def check_upload_file(client: FlaskClient, filename: str) -> None: - file = data_dir / filename - response = client.put( - "/opengeodeweb_back/upload_file", - data={"file": FileStorage(file.open("rb"))}, - ) - assert response.status_code == 201 + test_utils.check_upload_file(client, "test.og_brep") def test_upload_file_raw(client: FlaskClient) -> None: @@ -213,7 +203,7 @@ def get_full_data() -> test_utils.JsonData: def test_validate_object(client: FlaskClient) -> None: - response_save = check_save_viewable_file(client, "BRep", "cube.og_brep") + response_save = test_utils.check_save_viewable_file(client, "BRep", "cube.og_brep") assert response_save.status_code == 200 model_id = response_save.get_json()["id"] response = client.post("/opengeodeweb_back/validate", json={"id": model_id}) @@ -225,7 +215,9 @@ def test_validate_object(client: FlaskClient) -> None: def test_validate_invalid_object(client: FlaskClient) -> None: - response_save = check_save_viewable_file(client, "BRep", "wrong_boundary_surface_model.og_brep") + response_save = test_utils.check_save_viewable_file( + client, "BRep", "wrong_boundary_surface_model.og_brep" + ) assert response_save.status_code == 200 model_id = response_save.get_json()["id"] response = client.post("/opengeodeweb_back/validate", json={"id": model_id}) @@ -260,41 +252,7 @@ def get_full_data() -> test_utils.JsonData: def test_save_viewable_file(client: FlaskClient) -> TestResponse: - check_save_viewable_file(client, "BRep", "corbi.og_brep") - - -def check_save_viewable_file( - client: FlaskClient, - geode_object_type: str, - filename: str, -) -> TestResponse: - check_upload_file(client, filename) - route = "/opengeodeweb_back/save_viewable_file" - - def get_full_data() -> test_utils.JsonData: - return { - "geode_object_type": geode_object_type, - "filename": filename, - } - - # Normal test with filename 'corbi.og_brep' - response = client.post(route, json=get_full_data()) - assert response.status_code == 200 - native_file = response.get_json()["native_file"] - assert type(native_file) is str - viewable_file = response.get_json()["viewable_file"] - assert type(viewable_file) is str - data_id = response.get_json().get("id") - assert type(data_id) is str - object_type = response.get_json()["viewer_type"] - assert type(object_type) is str - assert object_type in ["model", "mesh"] - binary_light_viewable = response.get_json()["binary_light_viewable"] - assert type(binary_light_viewable) is str - - # Test all params - test_utils.test_route_wrong_params(client, route, get_full_data) - return response + test_utils.check_save_viewable_file(client, "BRep", "corbi.og_brep") def test_texture_coordinates(client: FlaskClient) -> None: @@ -523,7 +481,7 @@ def get_full_data() -> test_utils.JsonData: def test_model_components(client: FlaskClient) -> None: geode_object_type = "BRep" filename = "cube.og_brep" - response = check_save_viewable_file(client, geode_object_type, filename) + response = test_utils.check_save_viewable_file(client, geode_object_type, filename) assert response.status_code == 200 assert "mesh_components" in response.get_json() mesh_components = response.get_json()["mesh_components"] @@ -679,7 +637,7 @@ def test_save_viewable_workflow_from_object(client: FlaskClient) -> None: def _load_brep_components(client: FlaskClient) -> tuple[str, dict[str, list[str]]]: """Load cube.og_brep and return (model_id, {component_type: [geode_id, ...]}).""" - response = check_save_viewable_file(client, "BRep", "cube.og_brep") + response = test_utils.check_save_viewable_file(client, "BRep", "cube.og_brep") assert response.status_code == 200 model_id: str = response.get_json()["id"] mesh_components: list[dict[str, object]] = response.get_json()["mesh_components"] @@ -1032,6 +990,8 @@ def test_apply_time_series_errors(client: FlaskClient) -> None: response = client.post(route, json={"id": "0" * 32, "filename": filename}) assert response.status_code == 404 - point_set_id = check_save_viewable_file(client, "PointSet3D", "test.og_pts3d").get_json()["id"] + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] response = client.post(route, json={"id": point_set_id, "filename": filename}) assert response.status_code == 400 From 0304b1e3e633202eeef3bb908c7ce35c5b3e38e1 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 22 Oct 2026 14:46:03 +0200 Subject: [PATCH 02/10] add crs_manager to CRS-capable wrappers [skip ci] --- src/opengeodeweb_back/geode_objects/geode_brep.py | 13 +++++++++++++ .../geode_objects/geode_edged_curve2d.py | 3 +++ .../geode_objects/geode_edged_curve3d.py | 3 +++ .../geode_objects/geode_point_set2d.py | 3 +++ .../geode_objects/geode_point_set3d.py | 3 +++ .../geode_objects/geode_section.py | 12 ++++++++++++ .../geode_objects/geode_solid_mesh3d.py | 3 +++ .../geode_objects/geode_surface_mesh2d.py | 3 +++ .../geode_objects/geode_surface_mesh3d.py | 3 +++ 9 files changed, 46 insertions(+) diff --git a/src/opengeodeweb_back/geode_objects/geode_brep.py b/src/opengeodeweb_back/geode_objects/geode_brep.py index 21d5e172..f0fc9196 100644 --- a/src/opengeodeweb_back/geode_objects/geode_brep.py +++ b/src/opengeodeweb_back/geode_objects/geode_brep.py @@ -136,6 +136,19 @@ def inspect(self) -> og_inspector.BRepInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_brep_valid(self.brep) + def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: + # Models have no CRS manager of their own: CRS are assigned to every component mesh + for components in ( + self.brep.corners(), + self.brep.lines(), + self.brep.surfaces(), + self.brep.blocks(), + ): + for component in components: + return component.mesh().main_coordinate_reference_system_manager() + msg = "BRep has no mesh component" + raise ValueError(msg) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py b/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py index 6a6cd622..3bc50233 100644 --- a/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py @@ -103,6 +103,9 @@ def inspect(self) -> og_inspector.EdgedCurveInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_edged_curve_valid2D(self.edged_curve) + def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: + return self.edged_curve.main_coordinate_reference_system_manager() + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py b/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py index 24ee183d..18aade0d 100644 --- a/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py @@ -103,6 +103,9 @@ def inspect(self) -> og_inspector.EdgedCurveInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_edged_curve_valid3D(self.edged_curve) + def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: + return self.edged_curve.main_coordinate_reference_system_manager() + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_point_set2d.py b/src/opengeodeweb_back/geode_objects/geode_point_set2d.py index 7a423348..a770c9d7 100644 --- a/src/opengeodeweb_back/geode_objects/geode_point_set2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_point_set2d.py @@ -101,6 +101,9 @@ def inspect(self) -> og_inspector.PointSetInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_pointset_valid2D(self.point_set) + def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: + return self.point_set.main_coordinate_reference_system_manager() + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_point_set3d.py b/src/opengeodeweb_back/geode_objects/geode_point_set3d.py index fbfe030d..da49c938 100644 --- a/src/opengeodeweb_back/geode_objects/geode_point_set3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_point_set3d.py @@ -101,6 +101,9 @@ def inspect(self) -> og_inspector.PointSetInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_pointset_valid3D(self.point_set) + def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: + return self.point_set.main_coordinate_reference_system_manager() + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_section.py b/src/opengeodeweb_back/geode_objects/geode_section.py index c6b7a403..d4e84ce1 100644 --- a/src/opengeodeweb_back/geode_objects/geode_section.py +++ b/src/opengeodeweb_back/geode_objects/geode_section.py @@ -125,6 +125,18 @@ def inspect(self) -> og_inspector.SectionInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_section_valid(self.section) + def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: + # Models have no CRS manager of their own: CRS are assigned to every component mesh + for components in ( + self.section.corners(), + self.section.lines(), + self.section.surfaces(), + ): + for component in components: + return component.mesh().main_coordinate_reference_system_manager() + msg = "Section has no mesh component" + raise ValueError(msg) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py b/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py index 35ab866f..6b978031 100644 --- a/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py @@ -49,6 +49,9 @@ def inspect(self) -> og_inspector.SolidInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_solid_valid3D(self.solid_mesh) + def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: + return self.solid_mesh.main_coordinate_reference_system_manager() + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py b/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py index ceea9ed1..eb1d62f9 100644 --- a/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py @@ -49,6 +49,9 @@ def inspect(self) -> og_inspector.SurfaceInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_surface_valid2D(self.surface_mesh) + def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: + return self.surface_mesh.main_coordinate_reference_system_manager() + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py b/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py index 0679f494..1f172af3 100644 --- a/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py @@ -49,6 +49,9 @@ def inspect(self) -> og_inspector.SurfaceInspectionResult: def validate(self) -> og_inspector.ObjectValidity: return og_inspector.is_surface_valid3D(self.surface_mesh) + def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: + return self.surface_mesh.main_coordinate_reference_system_manager() + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: From c300513326048a5255566cfcf3a0c1474158e8d8 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 22 Oct 2026 14:47:22 +0200 Subject: [PATCH 03/10] feat(crs): CRS blueprint with geographic_list, active and assign_geographic --- opengeodeweb_back_schemas.json | 193 +++-- opengeodeweb_back_typed_schemas.d.ts | 731 ++++++++++-------- pyproject.toml | 1 + src/opengeodeweb_back/app.py | 6 + .../routes/blueprint_routes.py | 20 - src/opengeodeweb_back/routes/crs/__init__.py | 0 .../routes/crs/blueprint_crs.py | 84 ++ .../routes/crs/schemas/__init__.py | 3 + .../routes/crs/schemas/active.json | 50 ++ .../routes/crs/schemas/active.py | 42 + .../routes/crs/schemas/assign_geographic.json | 39 + .../routes/crs/schemas/assign_geographic.py | 33 + .../schemas/geographic_list.json} | 2 +- .../schemas/geographic_list.py} | 12 +- .../routes/schemas/__init__.py | 1 - tests/test_crs_routes.py | 94 +++ tests/test_routes.py | 19 - 17 files changed, 908 insertions(+), 422 deletions(-) create mode 100644 src/opengeodeweb_back/routes/crs/__init__.py create mode 100644 src/opengeodeweb_back/routes/crs/blueprint_crs.py create mode 100644 src/opengeodeweb_back/routes/crs/schemas/__init__.py create mode 100644 src/opengeodeweb_back/routes/crs/schemas/active.json create mode 100644 src/opengeodeweb_back/routes/crs/schemas/active.py create mode 100644 src/opengeodeweb_back/routes/crs/schemas/assign_geographic.json create mode 100644 src/opengeodeweb_back/routes/crs/schemas/assign_geographic.py rename src/opengeodeweb_back/routes/{schemas/geographic_coordinate_systems.json => crs/schemas/geographic_list.json} (95%) rename src/opengeodeweb_back/routes/{schemas/geographic_coordinate_systems.py => crs/schemas/geographic_list.py} (64%) create mode 100644 tests/test_crs_routes.py diff --git a/opengeodeweb_back_schemas.json b/opengeodeweb_back_schemas.json index 6f0d757d..a440388c 100644 --- a/opengeodeweb_back_schemas.json +++ b/opengeodeweb_back_schemas.json @@ -572,6 +572,149 @@ } } }, + "crs": { + "geographic_list": { + "$id": "opengeodeweb_back/crs/geographic_list", + "route": "/geographic_list", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "geode_object_type": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "geode_object_type" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": { + "crs_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "authority": { + "type": "string" + } + }, + "required": [ + "name", + "code", + "authority" + ], + "additionalProperties": false + } + } + }, + "required": [ + "crs_list" + ], + "additionalProperties": false + } + }, + "assign_geographic": { + "$id": "opengeodeweb_back/crs/assign_geographic", + "route": "/assign_geographic", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "authority": { + "type": "string", + "minLength": 1 + }, + "code": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "authority", + "code", + "name" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + } + }, + "active": { + "$id": "opengeodeweb_back/crs/active", + "route": "/active", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 32, + "maxLength": 32 + } + }, + "required": [ + "id" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": { + "crs_name": { + "type": "string" + }, + "crs_info": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false + } + }, + "required": [ + "crs_name" + ], + "additionalProperties": false + } + } + }, "vertex_attribute_range": { "$id": "opengeodeweb_back/vertex_attribute_range", "route": "/vertex_attribute_range", @@ -1819,56 +1962,6 @@ "additionalProperties": false } }, - "geographic_coordinate_systems": { - "$id": "opengeodeweb_back/geographic_coordinate_systems", - "route": "/geographic_coordinate_systems", - "methods": [ - "POST" - ], - "type": "object", - "properties": { - "geode_object_type": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "geode_object_type" - ], - "additionalProperties": false, - "response": { - "type": "object", - "properties": { - "crs_list": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "code": { - "type": "string" - }, - "authority": { - "type": "string" - } - }, - "required": [ - "name", - "code", - "authority" - ], - "additionalProperties": false - } - } - }, - "required": [ - "crs_list" - ], - "additionalProperties": false - } - }, "geode_objects_and_output_extensions": { "$id": "opengeodeweb_back/geode_objects_and_output_extensions", "route": "/geode_objects_and_output_extensions", diff --git a/opengeodeweb_back_typed_schemas.d.ts b/opengeodeweb_back_typed_schemas.d.ts index 74900e53..e087d88e 100644 --- a/opengeodeweb_back_typed_schemas.d.ts +++ b/opengeodeweb_back_typed_schemas.d.ts @@ -2,565 +2,584 @@ import type json from "./opengeodeweb_back_schemas.json"; export interface CreatePolygonalSurfaceParams { - name: string; - points: PolygonalSurfacePoint[]; - polygons: Array; + name: string; + points: PolygonalSurfacePoint[]; + polygons: Array; } export interface PolygonalSurfacePoint { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreatePolygonalSurfaceResponse { - binary_light_viewable?: string; - collection_components?: CreatePolygonalSurfaceResponseCollectionComponent[]; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreatePolygonalSurfaceResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreatePolygonalSurfaceResponseCollectionComponent[]; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreatePolygonalSurfaceResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreatePolygonalSurfaceResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreatePolygonalSurfaceResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface CreatePointSetParams { - name: string; - points: Point[]; + name: string; + points: Point[]; } export interface Point { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreatePointSetResponse { - binary_light_viewable?: string; - collection_components?: CreatePointSetResponseCollectionComponent[]; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreatePointSetResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreatePointSetResponseCollectionComponent[]; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreatePointSetResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreatePointSetResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreatePointSetResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface CreateEdgedCurveParams { - edges: Array; - name: string; - points: EdgedCurvePoint[]; + edges: Array; + name: string; + points: EdgedCurvePoint[]; } export interface EdgedCurvePoint { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreateEdgedCurveResponse { - binary_light_viewable?: string; - collection_components?: CreateEdgedCurveResponseCollectionComponent[]; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreateEdgedCurveResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreateEdgedCurveResponseCollectionComponent[]; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreateEdgedCurveResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreateEdgedCurveResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreateEdgedCurveResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; +} + +export interface CRSGeographicListParams { + geode_object_type: string; +} + +export interface CRSGeographicListResponse { + crs_list: CRSList[]; +} + +export interface CRSList { + authority: string; + code: string; + name: string; +} + +export interface CRSAssignGeographicParams { + authority: string; + code: string; + id: string; + name: string; +} + +export interface CRSAssignGeographicResponse {} + +export interface CRSActiveParams { + id: string; +} + +export interface CRSActiveResponse { + crs_info?: CRSInfo; + crs_name: string; +} + +export interface CRSInfo { + authority: string; + code: string; + name: string; } export interface VertexAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface VertexAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface VertexAttributeNamesParams { - id: string; + id: string; } export interface VertexAttributeNamesResponse { - attributes: VertexAttributeNamesResponseAttribute[]; + attributes: VertexAttributeNamesResponseAttribute[]; } export interface VertexAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ValidateParams { - id: string; + id: string; } export interface ValidateResponse { - is_valid: boolean; - issues: string[]; - nb_issues: number; + is_valid: boolean; + issues: string[]; + nb_issues: number; } export interface UploadFileParams { - filename?: string; + filename?: string; } export interface UploadFileResponse { - message: string; + message: string; } export interface TimeSeriesMissingFilesParams { - filename: string; + filename: string; } export interface TimeSeriesMissingFilesResponse { - additional_files: string[]; - has_missing_files: boolean; - mandatory_files: string[]; + additional_files: string[]; + has_missing_files: boolean; + mandatory_files: string[]; } export interface TimeSeriesAllowedObjectsParams { - filename: string; + filename: string; } export interface TimeSeriesAllowedObjectsResponse { - allowed_objects: string[]; + allowed_objects: string[]; } export interface TextureCoordinatesParams { - id: string; + id: string; } export interface TextureCoordinatesResponse { - texture_coordinates: string[]; + texture_coordinates: string[]; } export interface SaveViewableFileParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface SaveViewableFileResponse { - binary_light_viewable?: string; - collection_components?: SaveViewableFileResponseCollectionComponent[]; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: SaveViewableFileResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: SaveViewableFileResponseCollectionComponent[]; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: SaveViewableFileResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface SaveViewableFileResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface SaveViewableFileResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface PolyhedronAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface PolyhedronAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface PolyhedronAttributeNamesParams { - id: string; + id: string; } export interface PolyhedronAttributeNamesResponse { - attributes: PolyhedronAttributeNamesResponseAttribute[]; + attributes: PolyhedronAttributeNamesResponseAttribute[]; } export interface PolyhedronAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface PolygonAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface PolygonAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface PolygonAttributeNamesParams { - id: string; + id: string; } export interface PolygonAttributeNamesResponse { - attributes: PolygonAttributeNamesResponseAttribute[]; + attributes: PolygonAttributeNamesResponseAttribute[]; } export interface PolygonAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } -export interface PingParams { -} +export interface PingParams {} export interface PingResponse { - message: string; + message: string; } export interface ModelComponentVertexAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentVertexAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentVertexAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentVertexAttributeNamesResponse { - attributes: ModelComponentVertexAttributeNamesResponseAttribute[]; + attributes: ModelComponentVertexAttributeNamesResponseAttribute[]; } export interface ModelComponentVertexAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentPolyhedronAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolyhedronAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentPolyhedronAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolyhedronAttributeNamesResponse { - attributes: ModelComponentPolyhedronAttributeNamesResponseAttribute[]; + attributes: ModelComponentPolyhedronAttributeNamesResponseAttribute[]; } export interface ModelComponentPolyhedronAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentPolygonAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolygonAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentPolygonAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolygonAttributeNamesResponse { - attributes: ModelComponentPolygonAttributeNamesResponseAttribute[]; + attributes: ModelComponentPolygonAttributeNamesResponseAttribute[]; } export interface ModelComponentPolygonAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentEdgeAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentEdgeAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentEdgeAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentEdgeAttributeNamesResponse { - attributes: ModelComponentEdgeAttributeNamesResponseAttribute[]; + attributes: ModelComponentEdgeAttributeNamesResponseAttribute[]; } export interface ModelComponentEdgeAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } -export interface KillParams { -} +export interface KillParams {} export interface KillResponse { - message: string; + message: string; } -export interface ImportProjectParams { -} +export interface ImportProjectParams {} export interface ImportProjectResponse { - snapshot: { [key: string]: any }; -} - -export interface GeographicCoordinateSystemsParams { - geode_object_type: string; -} - -export interface GeographicCoordinateSystemsResponse { - crs_list: CRSList[]; -} - -export interface CRSList { - authority: string; - code: string; - name: string; + snapshot: { [key: string]: any }; } export interface GeodeObjectsAndOutputExtensionsParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface GeodeObjectsAndOutputExtensionsResponse { - geode_objects_and_output_extensions: { [key: string]: { [key: string]: boolean } }; + geode_objects_and_output_extensions: { [key: string]: { [key: string]: boolean } }; } export interface GeodeObjectInheritanceParams { - geode_object_type: string; + geode_object_type: string; } export interface GeodeObjectInheritanceResponse { - children: string[]; - parents: string[]; + children: string[]; + parents: string[]; } export interface ExportProjectParams { - filename: string; - snapshot: { [key: string]: any }; + filename: string; + snapshot: { [key: string]: any }; } export interface EdgeAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface EdgeAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface EdgeAttributeNamesParams { - id: string; + id: string; } export interface EdgeAttributeNamesResponse { - attributes: EdgeAttributeNamesResponseAttribute[]; + attributes: EdgeAttributeNamesResponseAttribute[]; } export interface EdgeAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface DataMissingFilesParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface DataMissingFilesResponse { - additional_files: string[]; - has_missing_files: boolean; - mandatory_files: string[]; + additional_files: string[]; + has_missing_files: boolean; + mandatory_files: string[]; } export interface CellAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface CellAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface CellAttributeNamesParams { - id: string; + id: string; } export interface CellAttributeNamesResponse { - attributes: CellAttributeNamesResponseAttribute[]; + attributes: CellAttributeNamesResponseAttribute[]; } export interface CellAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ApplyTimeSeriesParams { - filename: string; - id: string; + filename: string; + id: string; } -export interface ApplyTimeSeriesResponse { -} +export interface ApplyTimeSeriesResponse {} export interface AllowedObjectsParams { - filename: string; + filename: string; } export interface AllowedObjectsResponse { - allowed_objects: { [key: string]: AllowedObject }; + allowed_objects: { [key: string]: AllowedObject }; } export interface AllowedObject { - is_loadable: number; - object_priority: number; + is_loadable: number; + object_priority: number; } -export interface AllowedFilesParams { -} +export interface AllowedFilesParams {} export interface AllowedFilesResponse { - data_extensions: string[]; - time_series_extensions: string[]; + data_extensions: string[]; + time_series_extensions: string[]; } export interface ErrorResponse { - code: number; - description: string; - name: string; + code: number; + description: string; + name: string; } // `__params` and `__response` only exist at type level, to infer request/response types from a schema. @@ -573,45 +592,107 @@ export type TypedSchema = { export interface Schemas { readonly opengeodeweb_back: { readonly create: { - readonly polygonal_surface: (typeof json)["opengeodeweb_back"]["create"]["polygonal_surface"] & TypedSchema; - readonly point_set: (typeof json)["opengeodeweb_back"]["create"]["point_set"] & TypedSchema; - readonly edged_curve: (typeof json)["opengeodeweb_back"]["create"]["edged_curve"] & TypedSchema; + readonly polygonal_surface: (typeof json)["opengeodeweb_back"]["create"]["polygonal_surface"] & + TypedSchema; + readonly point_set: (typeof json)["opengeodeweb_back"]["create"]["point_set"] & + TypedSchema; + readonly edged_curve: (typeof json)["opengeodeweb_back"]["create"]["edged_curve"] & + TypedSchema; + }; + readonly crs: { + readonly geographic_list: (typeof json)["opengeodeweb_back"]["crs"]["geographic_list"] & + TypedSchema; + readonly assign_geographic: (typeof json)["opengeodeweb_back"]["crs"]["assign_geographic"] & + TypedSchema; + readonly active: (typeof json)["opengeodeweb_back"]["crs"]["active"] & + TypedSchema; }; - readonly vertex_attribute_range: (typeof json)["opengeodeweb_back"]["vertex_attribute_range"] & TypedSchema; - readonly vertex_attribute_names: (typeof json)["opengeodeweb_back"]["vertex_attribute_names"] & TypedSchema; - readonly validate: (typeof json)["opengeodeweb_back"]["validate"] & TypedSchema; - readonly upload_file: (typeof json)["opengeodeweb_back"]["upload_file"] & TypedSchema; - readonly time_series_missing_files: (typeof json)["opengeodeweb_back"]["time_series_missing_files"] & TypedSchema; - readonly time_series_allowed_objects: (typeof json)["opengeodeweb_back"]["time_series_allowed_objects"] & TypedSchema; - readonly texture_coordinates: (typeof json)["opengeodeweb_back"]["texture_coordinates"] & TypedSchema; - readonly save_viewable_file: (typeof json)["opengeodeweb_back"]["save_viewable_file"] & TypedSchema; - readonly polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_range"] & TypedSchema; - readonly polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_names"] & TypedSchema; - readonly polygon_attribute_range: (typeof json)["opengeodeweb_back"]["polygon_attribute_range"] & TypedSchema; - readonly polygon_attribute_names: (typeof json)["opengeodeweb_back"]["polygon_attribute_names"] & TypedSchema; - readonly ping: (typeof json)["opengeodeweb_back"]["ping"] & TypedSchema; - readonly model_component_vertex_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_range"] & TypedSchema; - readonly model_component_vertex_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_names"] & TypedSchema; - readonly model_component_polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_range"] & TypedSchema; - readonly model_component_polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_names"] & TypedSchema; - readonly model_component_polygon_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_range"] & TypedSchema; - readonly model_component_polygon_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_names"] & TypedSchema; - readonly model_component_edge_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_range"] & TypedSchema; - readonly model_component_edge_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_names"] & TypedSchema; - readonly kill: (typeof json)["opengeodeweb_back"]["kill"] & TypedSchema; - readonly import_project: (typeof json)["opengeodeweb_back"]["import_project"] & TypedSchema; - readonly geographic_coordinate_systems: (typeof json)["opengeodeweb_back"]["geographic_coordinate_systems"] & TypedSchema; - readonly geode_objects_and_output_extensions: (typeof json)["opengeodeweb_back"]["geode_objects_and_output_extensions"] & TypedSchema; - readonly geode_object_inheritance: (typeof json)["opengeodeweb_back"]["geode_object_inheritance"] & TypedSchema; - readonly export_project: (typeof json)["opengeodeweb_back"]["export_project"] & TypedSchema; - readonly edge_attribute_range: (typeof json)["opengeodeweb_back"]["edge_attribute_range"] & TypedSchema; - readonly edge_attribute_names: (typeof json)["opengeodeweb_back"]["edge_attribute_names"] & TypedSchema; - readonly data_missing_files: (typeof json)["opengeodeweb_back"]["data_missing_files"] & TypedSchema; - readonly cell_attribute_range: (typeof json)["opengeodeweb_back"]["cell_attribute_range"] & TypedSchema; - readonly cell_attribute_names: (typeof json)["opengeodeweb_back"]["cell_attribute_names"] & TypedSchema; - readonly apply_time_series: (typeof json)["opengeodeweb_back"]["apply_time_series"] & TypedSchema; - readonly allowed_objects: (typeof json)["opengeodeweb_back"]["allowed_objects"] & TypedSchema; - readonly allowed_files: (typeof json)["opengeodeweb_back"]["allowed_files"] & TypedSchema; + readonly vertex_attribute_range: (typeof json)["opengeodeweb_back"]["vertex_attribute_range"] & + TypedSchema; + readonly vertex_attribute_names: (typeof json)["opengeodeweb_back"]["vertex_attribute_names"] & + TypedSchema; + readonly validate: (typeof json)["opengeodeweb_back"]["validate"] & + TypedSchema; + readonly upload_file: (typeof json)["opengeodeweb_back"]["upload_file"] & + TypedSchema; + readonly time_series_missing_files: (typeof json)["opengeodeweb_back"]["time_series_missing_files"] & + TypedSchema; + readonly time_series_allowed_objects: (typeof json)["opengeodeweb_back"]["time_series_allowed_objects"] & + TypedSchema; + readonly texture_coordinates: (typeof json)["opengeodeweb_back"]["texture_coordinates"] & + TypedSchema; + readonly save_viewable_file: (typeof json)["opengeodeweb_back"]["save_viewable_file"] & + TypedSchema; + readonly polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_range"] & + TypedSchema; + readonly polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_names"] & + TypedSchema; + readonly polygon_attribute_range: (typeof json)["opengeodeweb_back"]["polygon_attribute_range"] & + TypedSchema; + readonly polygon_attribute_names: (typeof json)["opengeodeweb_back"]["polygon_attribute_names"] & + TypedSchema; + readonly ping: (typeof json)["opengeodeweb_back"]["ping"] & + TypedSchema; + readonly model_component_vertex_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_range"] & + TypedSchema< + ModelComponentVertexAttributeRangeParams, + ModelComponentVertexAttributeRangeResponse + >; + readonly model_component_vertex_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_names"] & + TypedSchema< + ModelComponentVertexAttributeNamesParams, + ModelComponentVertexAttributeNamesResponse + >; + readonly model_component_polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_range"] & + TypedSchema< + ModelComponentPolyhedronAttributeRangeParams, + ModelComponentPolyhedronAttributeRangeResponse + >; + readonly model_component_polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_names"] & + TypedSchema< + ModelComponentPolyhedronAttributeNamesParams, + ModelComponentPolyhedronAttributeNamesResponse + >; + readonly model_component_polygon_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_range"] & + TypedSchema< + ModelComponentPolygonAttributeRangeParams, + ModelComponentPolygonAttributeRangeResponse + >; + readonly model_component_polygon_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_names"] & + TypedSchema< + ModelComponentPolygonAttributeNamesParams, + ModelComponentPolygonAttributeNamesResponse + >; + readonly model_component_edge_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_range"] & + TypedSchema; + readonly model_component_edge_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_names"] & + TypedSchema; + readonly kill: (typeof json)["opengeodeweb_back"]["kill"] & + TypedSchema; + readonly import_project: (typeof json)["opengeodeweb_back"]["import_project"] & + TypedSchema; + readonly geode_objects_and_output_extensions: (typeof json)["opengeodeweb_back"]["geode_objects_and_output_extensions"] & + TypedSchema; + readonly geode_object_inheritance: (typeof json)["opengeodeweb_back"]["geode_object_inheritance"] & + TypedSchema; + readonly export_project: (typeof json)["opengeodeweb_back"]["export_project"] & + TypedSchema; + readonly edge_attribute_range: (typeof json)["opengeodeweb_back"]["edge_attribute_range"] & + TypedSchema; + readonly edge_attribute_names: (typeof json)["opengeodeweb_back"]["edge_attribute_names"] & + TypedSchema; + readonly data_missing_files: (typeof json)["opengeodeweb_back"]["data_missing_files"] & + TypedSchema; + readonly cell_attribute_range: (typeof json)["opengeodeweb_back"]["cell_attribute_range"] & + TypedSchema; + readonly cell_attribute_names: (typeof json)["opengeodeweb_back"]["cell_attribute_names"] & + TypedSchema; + readonly apply_time_series: (typeof json)["opengeodeweb_back"]["apply_time_series"] & + TypedSchema; + readonly allowed_objects: (typeof json)["opengeodeweb_back"]["allowed_objects"] & + TypedSchema; + readonly allowed_files: (typeof json)["opengeodeweb_back"]["allowed_files"] & + TypedSchema; }; } diff --git a/pyproject.toml b/pyproject.toml index ecc27d62..bd314c5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,4 +33,5 @@ where = ["src"] "opengeodeweb_back.routes.schemas" = ["*.json"] "opengeodeweb_back.routes.models.schemas" = ["*.json"] "opengeodeweb_back.routes.create.schemas" = ["*.json"] +"opengeodeweb_back.routes.crs.schemas" = ["*.json"] "opengeodeweb_back" = ["py.typed"] diff --git a/src/opengeodeweb_back/app.py b/src/opengeodeweb_back/app.py index 3ed639ef..3e9bb58f 100644 --- a/src/opengeodeweb_back/app.py +++ b/src/opengeodeweb_back/app.py @@ -20,6 +20,7 @@ from opengeodeweb_back import app_config, utils_functions from opengeodeweb_back.routes import blueprint_routes from opengeodeweb_back.routes.create import blueprint_create +from opengeodeweb_back.routes.crs import blueprint_crs if TYPE_CHECKING: from collections.abc import Generator @@ -171,6 +172,11 @@ def register_ogw_back_blueprints(app: flask.Flask) -> None: url_prefix="/opengeodeweb_back/create", name="opengeodeweb_create", ) + app.register_blueprint( + blueprint_crs.routes, + url_prefix="/opengeodeweb_back/crs", + name="opengeodeweb_crs", + ) def run_server(app: Flask) -> None: diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 99346430..76f4f0af 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -15,7 +15,6 @@ # Third party imports import flask import opengeode as og -import opengeode_geosciences as og_geosciences import opengeode_geosciencesio as og_geosciencesio # noqa: F401 (registers IO plugins) import opengeode_io as og_io # noqa: F401 (registers IO plugins) import werkzeug @@ -233,25 +232,6 @@ def time_series_missing_files( ) -@typed_route(routes, schemas.geographic_coordinate_systems_route) -def crs_converter_geographic_coordinate_systems( - params: schemas.GeographicCoordinateSystems, -) -> schemas.GeographicCoordinateSystemsResponse: - geode_object = geode_functions.geode_object_from_string(params.geode_object_type) - infos = ( - og_geosciences.GeographicCoordinateSystem3D.geographic_coordinate_systems() - if geode_object.is_3d() - else og_geosciences.GeographicCoordinateSystem2D.geographic_coordinate_systems() - ) - crs_list = [ - schemas.geographic_coordinate_systems.CRSList( - name=info.name, code=info.code, authority=info.authority - ) - for info in infos - ] - return schemas.GeographicCoordinateSystemsResponse(crs_list=crs_list) - - @typed_route(routes, schemas.validate_route) def validate_object(params: schemas.Validate) -> schemas.ValidateResponse: geode_object = geode_functions.load_geode_object(params.id) diff --git a/src/opengeodeweb_back/routes/crs/__init__.py b/src/opengeodeweb_back/routes/crs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/opengeodeweb_back/routes/crs/blueprint_crs.py b/src/opengeodeweb_back/routes/crs/blueprint_crs.py new file mode 100644 index 00000000..99c9288c --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/blueprint_crs.py @@ -0,0 +1,84 @@ +# Standard library imports + +# Third party imports +import flask +import opengeode_geosciences as og_geosciences + +# Local application imports +from opengeodeweb_back import geode_functions, utils_functions +from opengeodeweb_back.geode_objects.geode_brep import GeodeBRep +from opengeodeweb_back.geode_objects.geode_edged_curve2d import GeodeEdgedCurve2D +from opengeodeweb_back.geode_objects.geode_edged_curve3d import GeodeEdgedCurve3D +from opengeodeweb_back.geode_objects.geode_point_set2d import GeodePointSet2D +from opengeodeweb_back.geode_objects.geode_point_set3d import GeodePointSet3D +from opengeodeweb_back.geode_objects.geode_section import GeodeSection +from opengeodeweb_back.geode_objects.geode_solid_mesh3d import GeodeSolidMesh3D +from opengeodeweb_back.geode_objects.geode_surface_mesh2d import GeodeSurfaceMesh2D +from opengeodeweb_back.geode_objects.geode_surface_mesh3d import GeodeSurfaceMesh3D +from opengeodeweb_back.routes.crs import schemas +from opengeodeweb_back.typed_route import typed_route + +routes = flask.Blueprint("crs", __name__, url_prefix="/crs") + +CRS_GEODE_OBJECTS = ( + GeodeBRep, + GeodeSection, + GeodePointSet2D, + GeodePointSet3D, + GeodeEdgedCurve2D, + GeodeEdgedCurve3D, + GeodeSurfaceMesh2D, + GeodeSurfaceMesh3D, + GeodeSolidMesh3D, +) + + +@typed_route(routes, schemas.geographic_list_route) +def geographic_list(params: schemas.GeographicList) -> schemas.GeographicListResponse: + geode_object = geode_functions.geode_object_from_string(params.geode_object_type) + infos = ( + og_geosciences.GeographicCoordinateSystem3D.geographic_coordinate_systems() + if geode_object.is_3d() + else og_geosciences.GeographicCoordinateSystem2D.geographic_coordinate_systems() + ) + crs_list = [ + schemas.geographic_list.CRSList(name=info.name, code=info.code, authority=info.authority) + for info in infos + ] + return schemas.GeographicListResponse(crs_list=crs_list) + + +@typed_route(routes, schemas.active_route) +def active(params: schemas.Active) -> schemas.ActiveResponse: + geode_object = geode_functions.load_geode_object(params.id) + if not isinstance(geode_object, CRS_GEODE_OBJECTS): + flask.abort(400, f"{params.id} does not support coordinate reference systems") + crs_manager = geode_object.crs_manager() + crs = crs_manager.active_coordinate_reference_system() + crs_info = None + if isinstance( + crs, + og_geosciences.GeographicCoordinateSystem2D | og_geosciences.GeographicCoordinateSystem3D, + ): + info = crs.info() + crs_info = schemas.active.CRSInfo(authority=info.authority, code=info.code, name=info.name) + return schemas.ActiveResponse( + crs_name=crs_manager.active_coordinate_reference_system_name(), + crs_info=crs_info, + ) + + +@typed_route(routes, schemas.assign_geographic_route) +def assign_geographic(params: schemas.AssignGeographic) -> schemas.AssignGeographicResponse: + data = geode_functions.get_data_info(params.id) + geode_object = geode_functions.geode_object_from_string(data.geode_object).load( + geode_functions.data_file_path(params.id, data.native_file) + ) + if not isinstance(geode_object, CRS_GEODE_OBJECTS): + flask.abort(400, f"{params.id} does not support coordinate reference systems") + info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) + geode_object.assign_crs(info.authority_code(), info) + utils_functions.save_all_viewables_and_return_info( + geode_object, data, geode_functions.data_file_path(params.id) + ) + return schemas.AssignGeographicResponse() diff --git a/src/opengeodeweb_back/routes/crs/schemas/__init__.py b/src/opengeodeweb_back/routes/crs/schemas/__init__.py new file mode 100644 index 00000000..28bc91e2 --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/__init__.py @@ -0,0 +1,3 @@ +from .geographic_list import * +from .assign_geographic import * +from .active import * diff --git a/src/opengeodeweb_back/routes/crs/schemas/active.json b/src/opengeodeweb_back/routes/crs/schemas/active.json new file mode 100644 index 00000000..45992366 --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/active.json @@ -0,0 +1,50 @@ +{ + "route": "/active", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 32, + "maxLength": 32 + } + }, + "required": [ + "id" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": { + "crs_name": { + "type": "string" + }, + "crs_info": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false + } + }, + "required": [ + "crs_name" + ], + "additionalProperties": false + } +} diff --git a/src/opengeodeweb_back/routes/crs/schemas/active.py b/src/opengeodeweb_back/routes/crs/schemas/active.py new file mode 100644 index 00000000..ebb997b1 --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/active.py @@ -0,0 +1,42 @@ +from pathlib import Path +from opengeodeweb_microservice.schemas import Route, load_schema +from typing import Optional +from dataclasses_json import DataClassJsonMixin +from opengeodeweb_microservice.schemas import print_dataclass +from dataclasses import dataclass + + +@dataclass +class Active(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + id: str + + +@dataclass +class CRSInfo(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + name: str + + +@dataclass +class ActiveResponse(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + crs_name: str + crs_info: Optional[CRSInfo] = None + + +active_route = Route( + schema=load_schema(Path(__file__)), + params=Active, + response=ActiveResponse, +) + +__all__ = ["Active", "ActiveResponse", "active_route"] diff --git a/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.json b/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.json new file mode 100644 index 00000000..4052b452 --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.json @@ -0,0 +1,39 @@ +{ + "route": "/assign_geographic", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "authority": { + "type": "string", + "minLength": 1 + }, + "code": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "authority", + "code", + "name" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + } +} diff --git a/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.py b/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.py new file mode 100644 index 00000000..42b5590f --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.py @@ -0,0 +1,33 @@ +from pathlib import Path +from opengeodeweb_microservice.schemas import Route, load_schema +from dataclasses_json import DataClassJsonMixin +from opengeodeweb_microservice.schemas import print_dataclass +from dataclasses import dataclass + + +@dataclass +class AssignGeographic(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + id: str + name: str + + +@dataclass +class AssignGeographicResponse(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + pass + + +assign_geographic_route = Route( + schema=load_schema(Path(__file__)), + params=AssignGeographic, + response=AssignGeographicResponse, +) + +__all__ = ["AssignGeographic", "AssignGeographicResponse", "assign_geographic_route"] diff --git a/src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.json b/src/opengeodeweb_back/routes/crs/schemas/geographic_list.json similarity index 95% rename from src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.json rename to src/opengeodeweb_back/routes/crs/schemas/geographic_list.json index 0355b540..21db718c 100644 --- a/src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.json +++ b/src/opengeodeweb_back/routes/crs/schemas/geographic_list.json @@ -1,5 +1,5 @@ { - "route": "/geographic_coordinate_systems", + "route": "/geographic_list", "methods": [ "POST" ], diff --git a/src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.py b/src/opengeodeweb_back/routes/crs/schemas/geographic_list.py similarity index 64% rename from src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.py rename to src/opengeodeweb_back/routes/crs/schemas/geographic_list.py index 4b5d8214..d359497c 100644 --- a/src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.py +++ b/src/opengeodeweb_back/routes/crs/schemas/geographic_list.py @@ -7,7 +7,7 @@ @dataclass -class GeographicCoordinateSystems(DataClassJsonMixin): +class GeographicList(DataClassJsonMixin): def __post_init__(self) -> None: print_dataclass(self) @@ -25,17 +25,17 @@ def __post_init__(self) -> None: @dataclass -class GeographicCoordinateSystemsResponse(DataClassJsonMixin): +class GeographicListResponse(DataClassJsonMixin): def __post_init__(self) -> None: print_dataclass(self) crs_list: List[CRSList] -geographic_coordinate_systems_route = Route( +geographic_list_route = Route( schema=load_schema(Path(__file__)), - params=GeographicCoordinateSystems, - response=GeographicCoordinateSystemsResponse, + params=GeographicList, + response=GeographicListResponse, ) -__all__ = ["GeographicCoordinateSystems", "GeographicCoordinateSystemsResponse", "geographic_coordinate_systems_route"] +__all__ = ["GeographicList", "GeographicListResponse", "geographic_list_route"] diff --git a/src/opengeodeweb_back/routes/schemas/__init__.py b/src/opengeodeweb_back/routes/schemas/__init__.py index 9ee4b5c7..a25e4a5b 100644 --- a/src/opengeodeweb_back/routes/schemas/__init__.py +++ b/src/opengeodeweb_back/routes/schemas/__init__.py @@ -21,7 +21,6 @@ from .model_component_edge_attribute_names import * from .kill import * from .import_project import * -from .geographic_coordinate_systems import * from .geode_objects_and_output_extensions import * from .geode_object_inheritance import * from .export_project import * diff --git a/tests/test_crs_routes.py b/tests/test_crs_routes.py new file mode 100644 index 00000000..2c5ce4d0 --- /dev/null +++ b/tests/test_crs_routes.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +# Standard library imports +from typing import TYPE_CHECKING + +# Third party imports +# Local application imports +from opengeodeweb_back import test_utils + +if TYPE_CHECKING: + from flask.testing import FlaskClient + +LAMBERT = {"authority": "EPSG", "code": "2154", "name": "RGF93 v1 / Lambert-93"} +UTM = {"authority": "EPSG", "code": "32631", "name": "WGS 84 / UTM zone 31N"} + + +def test_geographic_list(client: FlaskClient) -> None: + route = "/opengeodeweb_back/crs/geographic_list" + + def get_full_data() -> test_utils.JsonData: + return { + "geode_object_type": "BRep", + } + + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + crs_list = response.get_json()["crs_list"] + assert type(crs_list) is list + for crs in crs_list: + assert type(crs) is dict + + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_active(client: FlaskClient) -> None: + route = "/opengeodeweb_back/crs/active" + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + + def get_full_data() -> test_utils.JsonData: + return {"id": point_set_id} + + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + assert response.get_json()["crs_name"] == "points" + assert "crs_info" not in response.get_json() + + test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_assign_geographic(client: FlaskClient) -> None: + route = "/opengeodeweb_back/crs/assign_geographic" + active_route = "/opengeodeweb_back/crs/active" + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + + def get_full_data() -> test_utils.JsonData: + return {"id": point_set_id, **LAMBERT} + + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + response = client.post(route, json={"id": point_set_id, **UTM}) + assert response.status_code == 200 + response = client.post(active_route, json={"id": point_set_id}) + assert response.get_json() == {"crs_name": "EPSG:32631", "crs_info": UTM} + + brep_id = test_utils.check_save_viewable_file(client, "BRep", "cube.og_brep").get_json()["id"] + response = client.post(route, json={"id": brep_id, **LAMBERT}) + assert response.status_code == 200 + response = client.post(active_route, json={"id": brep_id}) + assert response.get_json() == {"crs_name": "EPSG:2154", "crs_info": LAMBERT} + + test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_crs_errors(client: FlaskClient) -> None: + active_route = "/opengeodeweb_back/crs/active" + assign_route = "/opengeodeweb_back/crs/assign_geographic" + + response = client.post(active_route, json={"id": "0" * 32}) + assert response.status_code == 404 + response = client.post(assign_route, json={"id": "0" * 32, **LAMBERT}) + assert response.status_code == 404 + + grid_id = test_utils.check_save_viewable_file( + client, "LightRegularGrid3D", "test.og_lrgd3d" + ).get_json()["id"] + response = client.post(active_route, json={"id": grid_id}) + assert response.status_code == 400 + response = client.post(assign_route, json={"id": grid_id, **LAMBERT}) + assert response.status_code == 400 diff --git a/tests/test_routes.py b/tests/test_routes.py index 6510cc6c..aa35949e 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -183,25 +183,6 @@ def get_full_data() -> test_utils.JsonData: test_utils.test_route_wrong_params(client, route, get_full_data) -def test_geographic_coordinate_systems(client: FlaskClient) -> None: - route = "/opengeodeweb_back/geographic_coordinate_systems" - - def get_full_data() -> test_utils.JsonData: - return { - "geode_object_type": "BRep", - } - - response = client.post(route, json=get_full_data()) - assert response.status_code == 200 - crs_list = response.get_json()["crs_list"] - assert type(crs_list) is list - for crs in crs_list: - assert type(crs) is dict - - # Test all params - test_utils.test_route_wrong_params(client, route, get_full_data) - - def test_validate_object(client: FlaskClient) -> None: response_save = test_utils.check_save_viewable_file(client, "BRep", "cube.og_brep") assert response_save.status_code == 200 From a69bb7a1a02ab862840c502b0d54022bfaf87574 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 22 Oct 2026 16:37:27 +0200 Subject: [PATCH 04/10] Add delete_crs to CRS-capable wrappers [skip ci] --- .../geode_objects/geode_brep.py | 24 +++++++++++++++++++ .../geode_objects/geode_edged_curve2d.py | 4 ++++ .../geode_objects/geode_edged_curve3d.py | 4 ++++ .../geode_objects/geode_point_set2d.py | 4 ++++ .../geode_objects/geode_point_set3d.py | 4 ++++ .../geode_objects/geode_section.py | 19 +++++++++++++++ .../geode_objects/geode_solid_mesh3d.py | 4 ++++ .../geode_objects/geode_surface_mesh2d.py | 4 ++++ .../geode_objects/geode_surface_mesh3d.py | 4 ++++ 9 files changed, 71 insertions(+) diff --git a/src/opengeodeweb_back/geode_objects/geode_brep.py b/src/opengeodeweb_back/geode_objects/geode_brep.py index f0fc9196..f6656c4e 100644 --- a/src/opengeodeweb_back/geode_objects/geode_brep.py +++ b/src/opengeodeweb_back/geode_objects/geode_brep.py @@ -149,6 +149,30 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: msg = "BRep has no mesh component" raise ValueError(msg) + def delete_crs(self, crs_name: str) -> None: + # Models have no CRS manager of their own: the CRS lives on every component mesh + builder = self.builder() + for corner in self.brep.corners(): + crs_builder = builder.corner_mesh_builder( + corner + ).main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + for line in self.brep.lines(): + crs_builder = builder.line_mesh_builder( + line + ).main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + for surface in self.brep.surfaces(): + crs_builder = builder.surface_mesh_builder( + surface + ).main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + for block in self.brep.blocks(): + crs_builder = builder.block_mesh_builder( + block + ).main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py b/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py index 3bc50233..68406d27 100644 --- a/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py @@ -106,6 +106,10 @@ def validate(self) -> og_inspector.ObjectValidity: def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: return self.edged_curve.main_coordinate_reference_system_manager() + def delete_crs(self, crs_name: str) -> None: + crs_builder = self.builder().main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py b/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py index 18aade0d..9b7914d4 100644 --- a/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py @@ -106,6 +106,10 @@ def validate(self) -> og_inspector.ObjectValidity: def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: return self.edged_curve.main_coordinate_reference_system_manager() + def delete_crs(self, crs_name: str) -> None: + crs_builder = self.builder().main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_point_set2d.py b/src/opengeodeweb_back/geode_objects/geode_point_set2d.py index a770c9d7..96203af1 100644 --- a/src/opengeodeweb_back/geode_objects/geode_point_set2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_point_set2d.py @@ -104,6 +104,10 @@ def validate(self) -> og_inspector.ObjectValidity: def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: return self.point_set.main_coordinate_reference_system_manager() + def delete_crs(self, crs_name: str) -> None: + crs_builder = self.builder().main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_point_set3d.py b/src/opengeodeweb_back/geode_objects/geode_point_set3d.py index da49c938..56820126 100644 --- a/src/opengeodeweb_back/geode_objects/geode_point_set3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_point_set3d.py @@ -104,6 +104,10 @@ def validate(self) -> og_inspector.ObjectValidity: def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: return self.point_set.main_coordinate_reference_system_manager() + def delete_crs(self, crs_name: str) -> None: + crs_builder = self.builder().main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_section.py b/src/opengeodeweb_back/geode_objects/geode_section.py index d4e84ce1..24d372f5 100644 --- a/src/opengeodeweb_back/geode_objects/geode_section.py +++ b/src/opengeodeweb_back/geode_objects/geode_section.py @@ -137,6 +137,25 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: msg = "Section has no mesh component" raise ValueError(msg) + def delete_crs(self, crs_name: str) -> None: + # Models have no CRS manager of their own: the CRS lives on every component mesh + builder = self.builder() + for corner in self.section.corners(): + crs_builder = builder.corner_mesh_builder( + corner + ).main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + for line in self.section.lines(): + crs_builder = builder.line_mesh_builder( + line + ).main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + for surface in self.section.surfaces(): + crs_builder = builder.surface_mesh_builder( + surface + ).main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py b/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py index 6b978031..eb872b21 100644 --- a/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py @@ -52,6 +52,10 @@ def validate(self) -> og_inspector.ObjectValidity: def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: return self.solid_mesh.main_coordinate_reference_system_manager() + def delete_crs(self, crs_name: str) -> None: + crs_builder = self.builder().main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py b/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py index eb1d62f9..3f46bfd0 100644 --- a/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py @@ -52,6 +52,10 @@ def validate(self) -> og_inspector.ObjectValidity: def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: return self.surface_mesh.main_coordinate_reference_system_manager() + def delete_crs(self, crs_name: str) -> None: + crs_builder = self.builder().main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: diff --git a/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py b/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py index 1f172af3..3fe48a77 100644 --- a/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py @@ -52,6 +52,10 @@ def validate(self) -> og_inspector.ObjectValidity: def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: return self.surface_mesh.main_coordinate_reference_system_manager() + def delete_crs(self, crs_name: str) -> None: + crs_builder = self.builder().main_coordinate_reference_system_manager_builder() + crs_builder.delete_coordinate_reference_system(crs_name) + def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo ) -> None: From 723e2dca6d1d068125987549b60c72b6fbb9f243 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 22 Oct 2026 16:37:53 +0200 Subject: [PATCH 05/10] feat(crs): convert_geographic and align routes --- opengeodeweb_back_schemas.json | 132 ++++++++++++++ opengeodeweb_back_typed_schemas.d.ts | 38 ++++ .../routes/crs/blueprint_crs.py | 107 ++++++++++- .../routes/crs/schemas/__init__.py | 2 + .../routes/crs/schemas/align.json | 87 +++++++++ .../routes/crs/schemas/align.py | 55 ++++++ .../crs/schemas/convert_geographic.json | 43 +++++ .../routes/crs/schemas/convert_geographic.py | 34 ++++ tests/test_crs_routes.py | 171 +++++++++++++++++- 9 files changed, 660 insertions(+), 9 deletions(-) create mode 100644 src/opengeodeweb_back/routes/crs/schemas/align.json create mode 100644 src/opengeodeweb_back/routes/crs/schemas/align.py create mode 100644 src/opengeodeweb_back/routes/crs/schemas/convert_geographic.json create mode 100644 src/opengeodeweb_back/routes/crs/schemas/convert_geographic.py diff --git a/opengeodeweb_back_schemas.json b/opengeodeweb_back_schemas.json index a440388c..aecbe7d6 100644 --- a/opengeodeweb_back_schemas.json +++ b/opengeodeweb_back_schemas.json @@ -623,6 +623,50 @@ "additionalProperties": false } }, + "convert_geographic": { + "$id": "opengeodeweb_back/crs/convert_geographic", + "route": "/convert_geographic", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "authority": { + "type": "string", + "minLength": 1 + }, + "code": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "authority", + "code", + "name" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": { + "binary_light_viewable": { + "type": "string" + } + }, + "required": [], + "additionalProperties": false + } + }, "assign_geographic": { "$id": "opengeodeweb_back/crs/assign_geographic", "route": "/assign_geographic", @@ -663,6 +707,94 @@ "additionalProperties": false } }, + "align": { + "$id": "opengeodeweb_back/crs/align", + "route": "/align", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "source_points": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "x", + "y" + ], + "additionalProperties": false + } + }, + "target_points": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "x", + "y" + ], + "additionalProperties": false + } + }, + "authority": { + "type": "string", + "minLength": 1 + }, + "code": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "source_points", + "target_points", + "authority", + "code", + "name" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": { + "binary_light_viewable": { + "type": "string" + } + }, + "required": [], + "additionalProperties": false + } + }, "active": { "$id": "opengeodeweb_back/crs/active", "route": "/active", diff --git a/opengeodeweb_back_typed_schemas.d.ts b/opengeodeweb_back_typed_schemas.d.ts index e087d88e..4337f9f3 100644 --- a/opengeodeweb_back_typed_schemas.d.ts +++ b/opengeodeweb_back_typed_schemas.d.ts @@ -149,6 +149,17 @@ export interface CRSList { name: string; } +export interface CRSConvertGeographicParams { + authority: string; + code: string; + id: string; + name: string; +} + +export interface CRSConvertGeographicResponse { + binary_light_viewable?: string; +} + export interface CRSAssignGeographicParams { authority: string; code: string; @@ -158,6 +169,29 @@ export interface CRSAssignGeographicParams { export interface CRSAssignGeographicResponse {} +export interface CRSAlignParams { + authority: string; + code: string; + id: string; + name: string; + source_points: SourcePoint[]; + target_points: TargetPoint[]; +} + +export interface SourcePoint { + x: number; + y: number; +} + +export interface TargetPoint { + x: number; + y: number; +} + +export interface CRSAlignResponse { + binary_light_viewable?: string; +} + export interface CRSActiveParams { id: string; } @@ -602,8 +636,12 @@ export interface Schemas { readonly crs: { readonly geographic_list: (typeof json)["opengeodeweb_back"]["crs"]["geographic_list"] & TypedSchema; + readonly convert_geographic: (typeof json)["opengeodeweb_back"]["crs"]["convert_geographic"] & + TypedSchema; readonly assign_geographic: (typeof json)["opengeodeweb_back"]["crs"]["assign_geographic"] & TypedSchema; + readonly align: (typeof json)["opengeodeweb_back"]["crs"]["align"] & + TypedSchema; readonly active: (typeof json)["opengeodeweb_back"]["crs"]["active"] & TypedSchema; }; diff --git a/src/opengeodeweb_back/routes/crs/blueprint_crs.py b/src/opengeodeweb_back/routes/crs/blueprint_crs.py index 99c9288c..e0d87f05 100644 --- a/src/opengeodeweb_back/routes/crs/blueprint_crs.py +++ b/src/opengeodeweb_back/routes/crs/blueprint_crs.py @@ -2,6 +2,7 @@ # Third party imports import flask +import opengeode as og import opengeode_geosciences as og_geosciences # Local application imports @@ -33,6 +34,39 @@ ) +def _prepare_crs_name( + geode_object: GeodeBRep + | GeodeSection + | GeodePointSet2D + | GeodePointSet3D + | GeodeEdgedCurve2D + | GeodeEdgedCurve3D + | GeodeSurfaceMesh2D + | GeodeSurfaceMesh3D + | GeodeSolidMesh3D, + crs_name: str, +) -> None: + crs_manager = geode_object.crs_manager() + info = _geographic_info(crs_manager) + if info is not None and info.authority_code() == crs_name: + flask.abort(400, f"{crs_name} is already the active CRS") + # A registered inactive CRS may hold coordinates derived from an outdated label: recompute it + if crs_manager.coordinate_reference_system_exists(crs_name): + geode_object.delete_crs(crs_name) + + +def _geographic_info( + crs_manager: og.CoordinateReferenceSystemManager2D | og.CoordinateReferenceSystemManager3D, +) -> og_geosciences.GeographicCoordinateSystemInfo | None: + crs = crs_manager.active_coordinate_reference_system() + if isinstance( + crs, + og_geosciences.GeographicCoordinateSystem2D | og_geosciences.GeographicCoordinateSystem3D, + ): + return crs.info() + return None + + @typed_route(routes, schemas.geographic_list_route) def geographic_list(params: schemas.GeographicList) -> schemas.GeographicListResponse: geode_object = geode_functions.geode_object_from_string(params.geode_object_type) @@ -54,14 +88,12 @@ def active(params: schemas.Active) -> schemas.ActiveResponse: if not isinstance(geode_object, CRS_GEODE_OBJECTS): flask.abort(400, f"{params.id} does not support coordinate reference systems") crs_manager = geode_object.crs_manager() - crs = crs_manager.active_coordinate_reference_system() - crs_info = None - if isinstance( - crs, - og_geosciences.GeographicCoordinateSystem2D | og_geosciences.GeographicCoordinateSystem3D, - ): - info = crs.info() - crs_info = schemas.active.CRSInfo(authority=info.authority, code=info.code, name=info.name) + info = _geographic_info(crs_manager) + crs_info = ( + None + if info is None + else schemas.active.CRSInfo(authority=info.authority, code=info.code, name=info.name) + ) return schemas.ActiveResponse( crs_name=crs_manager.active_coordinate_reference_system_name(), crs_info=crs_info, @@ -77,8 +109,67 @@ def assign_geographic(params: schemas.AssignGeographic) -> schemas.AssignGeograp if not isinstance(geode_object, CRS_GEODE_OBJECTS): flask.abort(400, f"{params.id} does not support coordinate reference systems") info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) + _prepare_crs_name(geode_object, info.authority_code()) geode_object.assign_crs(info.authority_code(), info) utils_functions.save_all_viewables_and_return_info( geode_object, data, geode_functions.data_file_path(params.id) ) return schemas.AssignGeographicResponse() + + +@typed_route(routes, schemas.convert_geographic_route) +def convert_geographic(params: schemas.ConvertGeographic) -> schemas.ConvertGeographicResponse: + data = geode_functions.get_data_info(params.id) + geode_object = geode_functions.geode_object_from_string(data.geode_object).load( + geode_functions.data_file_path(params.id, data.native_file) + ) + if not isinstance(geode_object, CRS_GEODE_OBJECTS): + flask.abort(400, f"{params.id} does not support coordinate reference systems") + crs_manager = geode_object.crs_manager() + if _geographic_info(crs_manager) is None: + flask.abort(400, "Assign a geographic CRS before converting") + info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) + _prepare_crs_name(geode_object, info.authority_code()) + try: + geode_object.convert_crs(info.authority_code(), info) + except RuntimeError: # PROJ rejects coordinates outside the source or target CRS domain + flask.abort(400, f"Coordinates cannot be converted to {info.authority_code()}") + saved = utils_functions.save_all_viewables_and_return_info( + geode_object, data, geode_functions.data_file_path(params.id) + ) + # The front rebuilds its local actor from the new light viewable + return schemas.ConvertGeographicResponse( + binary_light_viewable=saved.get("binary_light_viewable") + ) + + +def _coordinate_system( + points: list[schemas.SourcePoint] | list[schemas.TargetPoint], +) -> og.CoordinateSystem2D: + origin, first, second = (og.Point2D([point.x, point.y]) for point in points) + return og.CoordinateSystem2D(origin, [first, second]) + + +@typed_route(routes, schemas.align_route) +def align(params: schemas.Align) -> schemas.AlignResponse: + data = geode_functions.get_data_info(params.id) + geode_object = geode_functions.geode_object_from_string(data.geode_object).load( + geode_functions.data_file_path(params.id, data.native_file) + ) + if not isinstance(geode_object, CRS_GEODE_OBJECTS): + flask.abort(400, f"{params.id} does not support coordinate reference systems") + if _geographic_info(geode_object.crs_manager()) is not None: + flask.abort(400, "Data already has a geographic CRS, use convert") + try: + source = _coordinate_system(params.source_points) + target = _coordinate_system(params.target_points) + except RuntimeError: # OpenGeode cannot build a coordinate system from colinear points + flask.abort(400, "Points must not be colinear") + geode_object.create_crs("aligned", source, target) + info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) + geode_object.assign_crs(info.authority_code(), info) + saved = utils_functions.save_all_viewables_and_return_info( + geode_object, data, geode_functions.data_file_path(params.id) + ) + # The front rebuilds its local actor from the new light viewable + return schemas.AlignResponse(binary_light_viewable=saved.get("binary_light_viewable")) diff --git a/src/opengeodeweb_back/routes/crs/schemas/__init__.py b/src/opengeodeweb_back/routes/crs/schemas/__init__.py index 28bc91e2..d5a0bc5d 100644 --- a/src/opengeodeweb_back/routes/crs/schemas/__init__.py +++ b/src/opengeodeweb_back/routes/crs/schemas/__init__.py @@ -1,3 +1,5 @@ from .geographic_list import * +from .convert_geographic import * from .assign_geographic import * +from .align import * from .active import * diff --git a/src/opengeodeweb_back/routes/crs/schemas/align.json b/src/opengeodeweb_back/routes/crs/schemas/align.json new file mode 100644 index 00000000..bbe3e8bd --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/align.json @@ -0,0 +1,87 @@ +{ + "route": "/align", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "source_points": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "x", + "y" + ], + "additionalProperties": false + } + }, + "target_points": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "items": { + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "x", + "y" + ], + "additionalProperties": false + } + }, + "authority": { + "type": "string", + "minLength": 1 + }, + "code": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "source_points", + "target_points", + "authority", + "code", + "name" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": { + "binary_light_viewable": { + "type": "string" + } + }, + "required": [], + "additionalProperties": false + } +} diff --git a/src/opengeodeweb_back/routes/crs/schemas/align.py b/src/opengeodeweb_back/routes/crs/schemas/align.py new file mode 100644 index 00000000..89bb6e1a --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/align.py @@ -0,0 +1,55 @@ +from pathlib import Path +from opengeodeweb_microservice.schemas import Route, load_schema +from typing import Optional +from dataclasses_json import DataClassJsonMixin +from opengeodeweb_microservice.schemas import print_dataclass +from dataclasses import dataclass +from typing import List + + +@dataclass +class SourcePoint(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + x: float + y: float + + +@dataclass +class TargetPoint(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + x: float + y: float + + +@dataclass +class Align(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + id: str + name: str + source_points: List[SourcePoint] + target_points: List[TargetPoint] + + +@dataclass +class AlignResponse(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + binary_light_viewable: Optional[str] = None + + +align_route = Route( + schema=load_schema(Path(__file__)), + params=Align, + response=AlignResponse, +) + +__all__ = ["SourcePoint", "TargetPoint", "Align", "AlignResponse", "align_route"] diff --git a/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.json b/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.json new file mode 100644 index 00000000..3113fdf7 --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.json @@ -0,0 +1,43 @@ +{ + "route": "/convert_geographic", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 32, + "maxLength": 32 + }, + "authority": { + "type": "string", + "minLength": 1 + }, + "code": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "authority", + "code", + "name" + ], + "additionalProperties": false, + "response": { + "type": "object", + "properties": { + "binary_light_viewable": { + "type": "string" + } + }, + "required": [], + "additionalProperties": false + } +} diff --git a/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.py b/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.py new file mode 100644 index 00000000..280ac867 --- /dev/null +++ b/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.py @@ -0,0 +1,34 @@ +from pathlib import Path +from opengeodeweb_microservice.schemas import Route, load_schema +from typing import Optional +from dataclasses_json import DataClassJsonMixin +from opengeodeweb_microservice.schemas import print_dataclass +from dataclasses import dataclass + + +@dataclass +class ConvertGeographic(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + id: str + name: str + + +@dataclass +class ConvertGeographicResponse(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + binary_light_viewable: Optional[str] = None + + +convert_geographic_route = Route( + schema=load_schema(Path(__file__)), + params=ConvertGeographic, + response=ConvertGeographicResponse, +) + +__all__ = ["ConvertGeographic", "ConvertGeographicResponse", "convert_geographic_route"] diff --git a/tests/test_crs_routes.py b/tests/test_crs_routes.py index 2c5ce4d0..b47c5b14 100644 --- a/tests/test_crs_routes.py +++ b/tests/test_crs_routes.py @@ -4,8 +4,11 @@ from typing import TYPE_CHECKING # Third party imports +import pytest + # Local application imports -from opengeodeweb_back import test_utils +from opengeodeweb_back import geode_functions, test_utils +from opengeodeweb_back.geode_objects.geode_point_set3d import GeodePointSet3D if TYPE_CHECKING: from flask.testing import FlaskClient @@ -92,3 +95,169 @@ def test_crs_errors(client: FlaskClient) -> None: assert response.status_code == 400 response = client.post(assign_route, json={"id": grid_id, **LAMBERT}) assert response.status_code == 400 + + +def _first_point(client: FlaskClient, data_id: str) -> list[float]: + with client.application.app_context(): + geode_object = geode_functions.load_geode_object(data_id) + assert isinstance(geode_object, GeodePointSet3D) + point = geode_object.point_set.point(0) + return [point.value(0), point.value(1), point.value(2)] + + +def test_convert_geographic(client: FlaskClient) -> None: + route = "/opengeodeweb_back/crs/convert_geographic" + assign_route = "/opengeodeweb_back/crs/assign_geographic" + active_route = "/opengeodeweb_back/crs/active" + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + client.post(assign_route, json={"id": point_set_id, **LAMBERT}) + lambert_point = _first_point(client, point_set_id) + + def get_full_data() -> test_utils.JsonData: + return {"id": point_set_id, **UTM} + + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + # The front rebuilds its local actor from the new light viewable + assert type(response.get_json()["binary_light_viewable"]) is str + response = client.post(active_route, json={"id": point_set_id}) + assert response.get_json() == {"crs_name": "EPSG:32631", "crs_info": UTM} + assert _first_point(client, point_set_id) != lambert_point + + response = client.post(route, json={"id": point_set_id, **LAMBERT}) + assert response.status_code == 200 + response = client.post(active_route, json={"id": point_set_id}) + assert response.get_json()["crs_name"] == "EPSG:2154" + assert _first_point(client, point_set_id) == pytest.approx(lambert_point, abs=1e-6) + + brep_id = test_utils.check_save_viewable_file(client, "BRep", "cube.og_brep").get_json()["id"] + client.post(assign_route, json={"id": brep_id, **LAMBERT}) + response = client.post(route, json={"id": brep_id, **UTM}) + assert response.status_code == 200 + response = client.post(active_route, json={"id": brep_id}) + assert response.get_json()["crs_name"] == "EPSG:32631" + + test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_convert_geographic_errors(client: FlaskClient) -> None: + route = "/opengeodeweb_back/crs/convert_geographic" + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + + response = client.post(route, json={"id": point_set_id, **UTM}) + assert response.status_code == 400 + + client.post("/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **UTM}) + response = client.post(route, json={"id": point_set_id, **UTM}) + assert response.status_code == 400 + + response = client.post(route, json={"id": "0" * 32, **UTM}) + assert response.status_code == 404 + + +SOURCE = [{"x": 0, "y": 0}, {"x": 10, "y": 0}, {"x": 0, "y": 10}] +TARGET = [{"x": 1000, "y": 2000}, {"x": 1000, "y": 2020}, {"x": 980, "y": 2000}] + + +def _create_point_set(client: FlaskClient) -> str: + points = [ + {"x": 0, "y": 0, "z": 5}, + {"x": 10, "y": 0, "z": 5}, + {"x": 0, "y": 10, "z": 5}, + ] + response = client.post( + "/opengeodeweb_back/create/point_set", json={"name": "align", "points": points} + ) + assert response.status_code == 200 + data_id: str = response.get_json()["id"] + return data_id + + +def test_align(client: FlaskClient) -> None: + route = "/opengeodeweb_back/crs/align" + point_set_id = _create_point_set(client) + + def get_full_data() -> test_utils.JsonData: + # Copies: test_route_wrong_params mutates the returned points + return { + "id": point_set_id, + "source_points": [dict(point) for point in SOURCE], + "target_points": [dict(point) for point in TARGET], + **LAMBERT, + } + + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + assert type(response.get_json()["binary_light_viewable"]) is str + response = client.post("/opengeodeweb_back/crs/active", json={"id": point_set_id}) + assert response.get_json() == {"crs_name": "EPSG:2154", "crs_info": LAMBERT} + assert _first_point(client, point_set_id) == [1000, 2000, 5] + + brep_id = test_utils.check_save_viewable_file(client, "BRep", "cube.og_brep").get_json()["id"] + response = client.post(route, json={**get_full_data(), "id": brep_id}) + assert response.status_code == 200 + + test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_align_errors(client: FlaskClient) -> None: + route = "/opengeodeweb_back/crs/align" + point_set_id = _create_point_set(client) + colinear = [{"x": 0, "y": 0}, {"x": 1, "y": 1}, {"x": 2, "y": 2}] + + response = client.post( + route, + json={"id": point_set_id, "source_points": colinear, "target_points": TARGET, **LAMBERT}, + ) + assert response.status_code == 400 + + client.post("/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **LAMBERT}) + response = client.post( + route, + json={"id": point_set_id, "source_points": SOURCE, "target_points": TARGET, **LAMBERT}, + ) + assert response.status_code == 400 + + +WGS84 = {"authority": "EPSG", "code": "4326", "name": "WGS 84"} +PSEUDO_MERCATOR = {"authority": "EPSG", "code": "3857", "name": "WGS 84 / Pseudo-Mercator"} + + +def test_reassign_after_convert(client: FlaskClient) -> None: + assign_route = "/opengeodeweb_back/crs/assign_geographic" + convert_route = "/opengeodeweb_back/crs/convert_geographic" + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + client.post(assign_route, json={"id": point_set_id, **LAMBERT}) + client.post(convert_route, json={"id": point_set_id, **UTM}) + stale_utm_point = _first_point(client, point_set_id) + + # EPSG:2154 is still registered (inactive) after the conversion + response = client.post(assign_route, json={"id": point_set_id, **LAMBERT}) + assert response.status_code == 200 + response = client.post(assign_route, json={"id": point_set_id, **LAMBERT}) + assert response.status_code == 400 + + # Relabelling then converting must reproject, not reactivate the stale EPSG:32631 + client.post(assign_route, json={"id": point_set_id, **PSEUDO_MERCATOR}) + response = client.post(convert_route, json={"id": point_set_id, **UTM}) + assert response.status_code == 200 + assert _first_point(client, point_set_id) != pytest.approx(stale_utm_point) + + +def test_convert_out_of_domain(client: FlaskClient) -> None: + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + convert_route = "/opengeodeweb_back/crs/convert_geographic" + client.post("/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **LAMBERT}) + client.post(convert_route, json={"id": point_set_id, **UTM}) + # UTM metres read as degrees give an invalid latitude + client.post("/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **WGS84}) + response = client.post(convert_route, json={"id": point_set_id, **LAMBERT}) + assert response.status_code == 400 From ece953dbd11d4766a54c1ff91be64f15ab0f4462 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Fri, 23 Oct 2026 09:34:38 +0000 Subject: [PATCH 06/10] Apply prepare changes --- .releaserc | 2 +- opengeodeweb_back_typed_schemas.d.ts | 750 +++++++++++++-------------- requirements.txt | 1 - 3 files changed, 349 insertions(+), 404 deletions(-) diff --git a/.releaserc b/.releaserc index bf47fdf5..30a901b9 100644 --- a/.releaserc +++ b/.releaserc @@ -6,6 +6,6 @@ plugins: [ '@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator', - '@semantic-release/github' + ['@semantic-release/github', { successCommentCondition: false, failCommentCondition: false }] ] } diff --git a/opengeodeweb_back_typed_schemas.d.ts b/opengeodeweb_back_typed_schemas.d.ts index 4337f9f3..67234df8 100644 --- a/opengeodeweb_back_typed_schemas.d.ts +++ b/opengeodeweb_back_typed_schemas.d.ts @@ -2,618 +2,624 @@ import type json from "./opengeodeweb_back_schemas.json"; export interface CreatePolygonalSurfaceParams { - name: string; - points: PolygonalSurfacePoint[]; - polygons: Array; + name: string; + points: PolygonalSurfacePoint[]; + polygons: Array; } export interface PolygonalSurfacePoint { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreatePolygonalSurfaceResponse { - binary_light_viewable?: string; - collection_components?: CreatePolygonalSurfaceResponseCollectionComponent[]; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreatePolygonalSurfaceResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreatePolygonalSurfaceResponseCollectionComponent[]; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreatePolygonalSurfaceResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreatePolygonalSurfaceResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreatePolygonalSurfaceResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface CreatePointSetParams { - name: string; - points: Point[]; + name: string; + points: Point[]; } export interface Point { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreatePointSetResponse { - binary_light_viewable?: string; - collection_components?: CreatePointSetResponseCollectionComponent[]; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreatePointSetResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreatePointSetResponseCollectionComponent[]; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreatePointSetResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreatePointSetResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreatePointSetResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface CreateEdgedCurveParams { - edges: Array; - name: string; - points: EdgedCurvePoint[]; + edges: Array; + name: string; + points: EdgedCurvePoint[]; } export interface EdgedCurvePoint { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreateEdgedCurveResponse { - binary_light_viewable?: string; - collection_components?: CreateEdgedCurveResponseCollectionComponent[]; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreateEdgedCurveResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreateEdgedCurveResponseCollectionComponent[]; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreateEdgedCurveResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreateEdgedCurveResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreateEdgedCurveResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface CRSGeographicListParams { - geode_object_type: string; + geode_object_type: string; } export interface CRSGeographicListResponse { - crs_list: CRSList[]; + crs_list: CRSList[]; } export interface CRSList { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface CRSConvertGeographicParams { - authority: string; - code: string; - id: string; - name: string; + authority: string; + code: string; + id: string; + name: string; } export interface CRSConvertGeographicResponse { - binary_light_viewable?: string; + binary_light_viewable?: string; } export interface CRSAssignGeographicParams { - authority: string; - code: string; - id: string; - name: string; + authority: string; + code: string; + id: string; + name: string; } -export interface CRSAssignGeographicResponse {} +export interface CRSAssignGeographicResponse { +} export interface CRSAlignParams { - authority: string; - code: string; - id: string; - name: string; - source_points: SourcePoint[]; - target_points: TargetPoint[]; + authority: string; + code: string; + id: string; + name: string; + source_points: SourcePoint[]; + target_points: TargetPoint[]; } export interface SourcePoint { - x: number; - y: number; + x: number; + y: number; } export interface TargetPoint { - x: number; - y: number; + x: number; + y: number; } export interface CRSAlignResponse { - binary_light_viewable?: string; + binary_light_viewable?: string; } export interface CRSActiveParams { - id: string; + id: string; } export interface CRSActiveResponse { - crs_info?: CRSInfo; - crs_name: string; + crs_info?: CRSInfo; + crs_name: string; } export interface CRSInfo { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface VertexAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface VertexAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface VertexAttributeNamesParams { - id: string; + id: string; } export interface VertexAttributeNamesResponse { - attributes: VertexAttributeNamesResponseAttribute[]; + attributes: VertexAttributeNamesResponseAttribute[]; } export interface VertexAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ValidateParams { - id: string; + id: string; } export interface ValidateResponse { - is_valid: boolean; - issues: string[]; - nb_issues: number; + is_valid: boolean; + issues: string[]; + nb_issues: number; } export interface UploadFileParams { - filename?: string; + filename?: string; } export interface UploadFileResponse { - message: string; + message: string; } export interface TimeSeriesMissingFilesParams { - filename: string; + filename: string; } export interface TimeSeriesMissingFilesResponse { - additional_files: string[]; - has_missing_files: boolean; - mandatory_files: string[]; + additional_files: string[]; + has_missing_files: boolean; + mandatory_files: string[]; } export interface TimeSeriesAllowedObjectsParams { - filename: string; + filename: string; } export interface TimeSeriesAllowedObjectsResponse { - allowed_objects: string[]; + allowed_objects: string[]; } export interface TextureCoordinatesParams { - id: string; + id: string; } export interface TextureCoordinatesResponse { - texture_coordinates: string[]; + texture_coordinates: string[]; } export interface SaveViewableFileParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface SaveViewableFileResponse { - binary_light_viewable?: string; - collection_components?: SaveViewableFileResponseCollectionComponent[]; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: SaveViewableFileResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: SaveViewableFileResponseCollectionComponent[]; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: SaveViewableFileResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface SaveViewableFileResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface SaveViewableFileResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface PolyhedronAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface PolyhedronAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface PolyhedronAttributeNamesParams { - id: string; + id: string; } export interface PolyhedronAttributeNamesResponse { - attributes: PolyhedronAttributeNamesResponseAttribute[]; + attributes: PolyhedronAttributeNamesResponseAttribute[]; } export interface PolyhedronAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface PolygonAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface PolygonAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface PolygonAttributeNamesParams { - id: string; + id: string; } export interface PolygonAttributeNamesResponse { - attributes: PolygonAttributeNamesResponseAttribute[]; + attributes: PolygonAttributeNamesResponseAttribute[]; } export interface PolygonAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } -export interface PingParams {} +export interface PingParams { +} export interface PingResponse { - message: string; + message: string; } export interface ModelComponentVertexAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentVertexAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentVertexAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentVertexAttributeNamesResponse { - attributes: ModelComponentVertexAttributeNamesResponseAttribute[]; + attributes: ModelComponentVertexAttributeNamesResponseAttribute[]; } export interface ModelComponentVertexAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentPolyhedronAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolyhedronAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentPolyhedronAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolyhedronAttributeNamesResponse { - attributes: ModelComponentPolyhedronAttributeNamesResponseAttribute[]; + attributes: ModelComponentPolyhedronAttributeNamesResponseAttribute[]; } export interface ModelComponentPolyhedronAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentPolygonAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolygonAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentPolygonAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolygonAttributeNamesResponse { - attributes: ModelComponentPolygonAttributeNamesResponseAttribute[]; + attributes: ModelComponentPolygonAttributeNamesResponseAttribute[]; } export interface ModelComponentPolygonAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentEdgeAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentEdgeAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentEdgeAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentEdgeAttributeNamesResponse { - attributes: ModelComponentEdgeAttributeNamesResponseAttribute[]; + attributes: ModelComponentEdgeAttributeNamesResponseAttribute[]; } export interface ModelComponentEdgeAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } -export interface KillParams {} +export interface KillParams { +} export interface KillResponse { - message: string; + message: string; } -export interface ImportProjectParams {} +export interface ImportProjectParams { +} export interface ImportProjectResponse { - snapshot: { [key: string]: any }; + snapshot: { [key: string]: any }; } export interface GeodeObjectsAndOutputExtensionsParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface GeodeObjectsAndOutputExtensionsResponse { - geode_objects_and_output_extensions: { [key: string]: { [key: string]: boolean } }; + geode_objects_and_output_extensions: { [key: string]: { [key: string]: boolean } }; } export interface GeodeObjectInheritanceParams { - geode_object_type: string; + geode_object_type: string; } export interface GeodeObjectInheritanceResponse { - children: string[]; - parents: string[]; + children: string[]; + parents: string[]; } export interface ExportProjectParams { - filename: string; - snapshot: { [key: string]: any }; + filename: string; + snapshot: { [key: string]: any }; } export interface EdgeAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface EdgeAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface EdgeAttributeNamesParams { - id: string; + id: string; } export interface EdgeAttributeNamesResponse { - attributes: EdgeAttributeNamesResponseAttribute[]; + attributes: EdgeAttributeNamesResponseAttribute[]; } export interface EdgeAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface DataMissingFilesParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface DataMissingFilesResponse { - additional_files: string[]; - has_missing_files: boolean; - mandatory_files: string[]; + additional_files: string[]; + has_missing_files: boolean; + mandatory_files: string[]; } export interface CellAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface CellAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface CellAttributeNamesParams { - id: string; + id: string; } export interface CellAttributeNamesResponse { - attributes: CellAttributeNamesResponseAttribute[]; + attributes: CellAttributeNamesResponseAttribute[]; } export interface CellAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ApplyTimeSeriesParams { - filename: string; - id: string; + filename: string; + id: string; } -export interface ApplyTimeSeriesResponse {} +export interface ApplyTimeSeriesResponse { +} export interface AllowedObjectsParams { - filename: string; + filename: string; } export interface AllowedObjectsResponse { - allowed_objects: { [key: string]: AllowedObject }; + allowed_objects: { [key: string]: AllowedObject }; } export interface AllowedObject { - is_loadable: number; - object_priority: number; + is_loadable: number; + object_priority: number; } -export interface AllowedFilesParams {} +export interface AllowedFilesParams { +} export interface AllowedFilesResponse { - data_extensions: string[]; - time_series_extensions: string[]; + data_extensions: string[]; + time_series_extensions: string[]; } export interface ErrorResponse { - code: number; - description: string; - name: string; + code: number; + description: string; + name: string; } // `__params` and `__response` only exist at type level, to infer request/response types from a schema. @@ -626,111 +632,51 @@ export type TypedSchema = { export interface Schemas { readonly opengeodeweb_back: { readonly create: { - readonly polygonal_surface: (typeof json)["opengeodeweb_back"]["create"]["polygonal_surface"] & - TypedSchema; - readonly point_set: (typeof json)["opengeodeweb_back"]["create"]["point_set"] & - TypedSchema; - readonly edged_curve: (typeof json)["opengeodeweb_back"]["create"]["edged_curve"] & - TypedSchema; + readonly polygonal_surface: (typeof json)["opengeodeweb_back"]["create"]["polygonal_surface"] & TypedSchema; + readonly point_set: (typeof json)["opengeodeweb_back"]["create"]["point_set"] & TypedSchema; + readonly edged_curve: (typeof json)["opengeodeweb_back"]["create"]["edged_curve"] & TypedSchema; }; readonly crs: { - readonly geographic_list: (typeof json)["opengeodeweb_back"]["crs"]["geographic_list"] & - TypedSchema; - readonly convert_geographic: (typeof json)["opengeodeweb_back"]["crs"]["convert_geographic"] & - TypedSchema; - readonly assign_geographic: (typeof json)["opengeodeweb_back"]["crs"]["assign_geographic"] & - TypedSchema; - readonly align: (typeof json)["opengeodeweb_back"]["crs"]["align"] & - TypedSchema; - readonly active: (typeof json)["opengeodeweb_back"]["crs"]["active"] & - TypedSchema; + readonly geographic_list: (typeof json)["opengeodeweb_back"]["crs"]["geographic_list"] & TypedSchema; + readonly convert_geographic: (typeof json)["opengeodeweb_back"]["crs"]["convert_geographic"] & TypedSchema; + readonly assign_geographic: (typeof json)["opengeodeweb_back"]["crs"]["assign_geographic"] & TypedSchema; + readonly align: (typeof json)["opengeodeweb_back"]["crs"]["align"] & TypedSchema; + readonly active: (typeof json)["opengeodeweb_back"]["crs"]["active"] & TypedSchema; }; - readonly vertex_attribute_range: (typeof json)["opengeodeweb_back"]["vertex_attribute_range"] & - TypedSchema; - readonly vertex_attribute_names: (typeof json)["opengeodeweb_back"]["vertex_attribute_names"] & - TypedSchema; - readonly validate: (typeof json)["opengeodeweb_back"]["validate"] & - TypedSchema; - readonly upload_file: (typeof json)["opengeodeweb_back"]["upload_file"] & - TypedSchema; - readonly time_series_missing_files: (typeof json)["opengeodeweb_back"]["time_series_missing_files"] & - TypedSchema; - readonly time_series_allowed_objects: (typeof json)["opengeodeweb_back"]["time_series_allowed_objects"] & - TypedSchema; - readonly texture_coordinates: (typeof json)["opengeodeweb_back"]["texture_coordinates"] & - TypedSchema; - readonly save_viewable_file: (typeof json)["opengeodeweb_back"]["save_viewable_file"] & - TypedSchema; - readonly polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_range"] & - TypedSchema; - readonly polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_names"] & - TypedSchema; - readonly polygon_attribute_range: (typeof json)["opengeodeweb_back"]["polygon_attribute_range"] & - TypedSchema; - readonly polygon_attribute_names: (typeof json)["opengeodeweb_back"]["polygon_attribute_names"] & - TypedSchema; - readonly ping: (typeof json)["opengeodeweb_back"]["ping"] & - TypedSchema; - readonly model_component_vertex_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_range"] & - TypedSchema< - ModelComponentVertexAttributeRangeParams, - ModelComponentVertexAttributeRangeResponse - >; - readonly model_component_vertex_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_names"] & - TypedSchema< - ModelComponentVertexAttributeNamesParams, - ModelComponentVertexAttributeNamesResponse - >; - readonly model_component_polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_range"] & - TypedSchema< - ModelComponentPolyhedronAttributeRangeParams, - ModelComponentPolyhedronAttributeRangeResponse - >; - readonly model_component_polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_names"] & - TypedSchema< - ModelComponentPolyhedronAttributeNamesParams, - ModelComponentPolyhedronAttributeNamesResponse - >; - readonly model_component_polygon_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_range"] & - TypedSchema< - ModelComponentPolygonAttributeRangeParams, - ModelComponentPolygonAttributeRangeResponse - >; - readonly model_component_polygon_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_names"] & - TypedSchema< - ModelComponentPolygonAttributeNamesParams, - ModelComponentPolygonAttributeNamesResponse - >; - readonly model_component_edge_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_range"] & - TypedSchema; - readonly model_component_edge_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_names"] & - TypedSchema; - readonly kill: (typeof json)["opengeodeweb_back"]["kill"] & - TypedSchema; - readonly import_project: (typeof json)["opengeodeweb_back"]["import_project"] & - TypedSchema; - readonly geode_objects_and_output_extensions: (typeof json)["opengeodeweb_back"]["geode_objects_and_output_extensions"] & - TypedSchema; - readonly geode_object_inheritance: (typeof json)["opengeodeweb_back"]["geode_object_inheritance"] & - TypedSchema; - readonly export_project: (typeof json)["opengeodeweb_back"]["export_project"] & - TypedSchema; - readonly edge_attribute_range: (typeof json)["opengeodeweb_back"]["edge_attribute_range"] & - TypedSchema; - readonly edge_attribute_names: (typeof json)["opengeodeweb_back"]["edge_attribute_names"] & - TypedSchema; - readonly data_missing_files: (typeof json)["opengeodeweb_back"]["data_missing_files"] & - TypedSchema; - readonly cell_attribute_range: (typeof json)["opengeodeweb_back"]["cell_attribute_range"] & - TypedSchema; - readonly cell_attribute_names: (typeof json)["opengeodeweb_back"]["cell_attribute_names"] & - TypedSchema; - readonly apply_time_series: (typeof json)["opengeodeweb_back"]["apply_time_series"] & - TypedSchema; - readonly allowed_objects: (typeof json)["opengeodeweb_back"]["allowed_objects"] & - TypedSchema; - readonly allowed_files: (typeof json)["opengeodeweb_back"]["allowed_files"] & - TypedSchema; + readonly vertex_attribute_range: (typeof json)["opengeodeweb_back"]["vertex_attribute_range"] & TypedSchema; + readonly vertex_attribute_names: (typeof json)["opengeodeweb_back"]["vertex_attribute_names"] & TypedSchema; + readonly validate: (typeof json)["opengeodeweb_back"]["validate"] & TypedSchema; + readonly upload_file: (typeof json)["opengeodeweb_back"]["upload_file"] & TypedSchema; + readonly time_series_missing_files: (typeof json)["opengeodeweb_back"]["time_series_missing_files"] & TypedSchema; + readonly time_series_allowed_objects: (typeof json)["opengeodeweb_back"]["time_series_allowed_objects"] & TypedSchema; + readonly texture_coordinates: (typeof json)["opengeodeweb_back"]["texture_coordinates"] & TypedSchema; + readonly save_viewable_file: (typeof json)["opengeodeweb_back"]["save_viewable_file"] & TypedSchema; + readonly polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_range"] & TypedSchema; + readonly polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_names"] & TypedSchema; + readonly polygon_attribute_range: (typeof json)["opengeodeweb_back"]["polygon_attribute_range"] & TypedSchema; + readonly polygon_attribute_names: (typeof json)["opengeodeweb_back"]["polygon_attribute_names"] & TypedSchema; + readonly ping: (typeof json)["opengeodeweb_back"]["ping"] & TypedSchema; + readonly model_component_vertex_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_range"] & TypedSchema; + readonly model_component_vertex_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_names"] & TypedSchema; + readonly model_component_polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_range"] & TypedSchema; + readonly model_component_polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_names"] & TypedSchema; + readonly model_component_polygon_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_range"] & TypedSchema; + readonly model_component_polygon_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_names"] & TypedSchema; + readonly model_component_edge_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_range"] & TypedSchema; + readonly model_component_edge_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_names"] & TypedSchema; + readonly kill: (typeof json)["opengeodeweb_back"]["kill"] & TypedSchema; + readonly import_project: (typeof json)["opengeodeweb_back"]["import_project"] & TypedSchema; + readonly geode_objects_and_output_extensions: (typeof json)["opengeodeweb_back"]["geode_objects_and_output_extensions"] & TypedSchema; + readonly geode_object_inheritance: (typeof json)["opengeodeweb_back"]["geode_object_inheritance"] & TypedSchema; + readonly export_project: (typeof json)["opengeodeweb_back"]["export_project"] & TypedSchema; + readonly edge_attribute_range: (typeof json)["opengeodeweb_back"]["edge_attribute_range"] & TypedSchema; + readonly edge_attribute_names: (typeof json)["opengeodeweb_back"]["edge_attribute_names"] & TypedSchema; + readonly data_missing_files: (typeof json)["opengeodeweb_back"]["data_missing_files"] & TypedSchema; + readonly cell_attribute_range: (typeof json)["opengeodeweb_back"]["cell_attribute_range"] & TypedSchema; + readonly cell_attribute_names: (typeof json)["opengeodeweb_back"]["cell_attribute_names"] & TypedSchema; + readonly apply_time_series: (typeof json)["opengeodeweb_back"]["apply_time_series"] & TypedSchema; + readonly allowed_objects: (typeof json)["opengeodeweb_back"]["allowed_objects"] & TypedSchema; + readonly allowed_files: (typeof json)["opengeodeweb_back"]["allowed_files"] & TypedSchema; }; } diff --git a/requirements.txt b/requirements.txt index fa4f895c..88997f0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -70,4 +70,3 @@ werkzeug==3.1.8 # flask # flask-cors -opengeodeweb-microservice==1.*,>=1.3.2 From f44887b6fb8d8d05b420c17de1bd19651b97ea2b Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 23 Oct 2026 11:35:44 +0200 Subject: [PATCH 07/10] feat(crs): active CRS in data responses --- opengeodeweb_back_schemas.json | 143 +++++++++++++++++- opengeodeweb_back_typed_schemas.d.ts | 52 ++++++- src/opengeodeweb_back/crs_functions.py | 72 +++++++++ .../routes/create/schemas/edged_curve.json | 22 ++- .../routes/create/schemas/edged_curve.py | 11 ++ .../routes/create/schemas/point_set.json | 22 ++- .../routes/create/schemas/point_set.py | 11 ++ .../create/schemas/polygonal_surface.json | 22 ++- .../create/schemas/polygonal_surface.py | 11 ++ .../routes/crs/blueprint_crs.py | 68 +++------ .../routes/crs/schemas/align.json | 20 +++ .../routes/crs/schemas/align.py | 11 ++ .../routes/crs/schemas/assign_geographic.json | 23 ++- .../routes/crs/schemas/assign_geographic.py | 13 +- .../crs/schemas/convert_geographic.json | 20 +++ .../routes/crs/schemas/convert_geographic.py | 11 ++ .../routes/schemas/save_viewable_file.json | 22 ++- .../routes/schemas/save_viewable_file.py | 11 ++ src/opengeodeweb_back/utils_functions.py | 4 + tests/test_crs_routes.py | 32 ++++ tests/test_routes.py | 5 +- 21 files changed, 546 insertions(+), 60 deletions(-) create mode 100644 src/opengeodeweb_back/crs_functions.py diff --git a/opengeodeweb_back_schemas.json b/opengeodeweb_back_schemas.json index aecbe7d6..b464a7d4 100644 --- a/opengeodeweb_back_schemas.json +++ b/opengeodeweb_back_schemas.json @@ -181,6 +181,26 @@ "type": "string", "minLength": 36, "maxLength": 36 + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [ @@ -362,6 +382,26 @@ "type": "string", "minLength": 36, "maxLength": 36 + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [ @@ -557,6 +597,26 @@ "type": "string", "minLength": 36, "maxLength": 36 + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [ @@ -661,6 +721,26 @@ "properties": { "binary_light_viewable": { "type": "string" + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [], @@ -702,7 +782,28 @@ "additionalProperties": false, "response": { "type": "object", - "properties": {}, + "properties": { + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false + } + }, "required": [], "additionalProperties": false } @@ -789,6 +890,26 @@ "properties": { "binary_light_viewable": { "type": "string" + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [], @@ -1283,6 +1404,26 @@ "type": "string", "minLength": 36, "maxLength": 36 + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [ diff --git a/opengeodeweb_back_typed_schemas.d.ts b/opengeodeweb_back_typed_schemas.d.ts index 4337f9f3..d0b7c375 100644 --- a/opengeodeweb_back_typed_schemas.d.ts +++ b/opengeodeweb_back_typed_schemas.d.ts @@ -16,6 +16,7 @@ export interface PolygonalSurfacePoint { export interface CreatePolygonalSurfaceResponse { binary_light_viewable?: string; collection_components?: CreatePolygonalSurfaceResponseCollectionComponent[]; + crs?: CreatePolygonalSurfaceResponseCRS; geode_id: string; geode_object_type: string; id: string; @@ -36,6 +37,12 @@ export interface CreatePolygonalSurfaceResponseCollectionComponent { type: string; } +export interface CreatePolygonalSurfaceResponseCRS { + authority: string; + code: string; + name: string; +} + export interface CreatePolygonalSurfaceResponseMeshComponent { boundaries: string[]; geode_id: string; @@ -60,6 +67,7 @@ export interface Point { export interface CreatePointSetResponse { binary_light_viewable?: string; collection_components?: CreatePointSetResponseCollectionComponent[]; + crs?: CreatePointSetResponseCRS; geode_id: string; geode_object_type: string; id: string; @@ -80,6 +88,12 @@ export interface CreatePointSetResponseCollectionComponent { type: string; } +export interface CreatePointSetResponseCRS { + authority: string; + code: string; + name: string; +} + export interface CreatePointSetResponseMeshComponent { boundaries: string[]; geode_id: string; @@ -105,6 +119,7 @@ export interface EdgedCurvePoint { export interface CreateEdgedCurveResponse { binary_light_viewable?: string; collection_components?: CreateEdgedCurveResponseCollectionComponent[]; + crs?: CreateEdgedCurveResponseCRS; geode_id: string; geode_object_type: string; id: string; @@ -125,6 +140,12 @@ export interface CreateEdgedCurveResponseCollectionComponent { type: string; } +export interface CreateEdgedCurveResponseCRS { + authority: string; + code: string; + name: string; +} + export interface CreateEdgedCurveResponseMeshComponent { boundaries: string[]; geode_id: string; @@ -158,6 +179,13 @@ export interface CRSConvertGeographicParams { export interface CRSConvertGeographicResponse { binary_light_viewable?: string; + crs?: CRSConvertGeographicResponseCRS; +} + +export interface CRSConvertGeographicResponseCRS { + authority: string; + code: string; + name: string; } export interface CRSAssignGeographicParams { @@ -167,7 +195,15 @@ export interface CRSAssignGeographicParams { name: string; } -export interface CRSAssignGeographicResponse {} +export interface CRSAssignGeographicResponse { + crs?: CRSAssignGeographicResponseCRS; +} + +export interface CRSAssignGeographicResponseCRS { + authority: string; + code: string; + name: string; +} export interface CRSAlignParams { authority: string; @@ -190,6 +226,13 @@ export interface TargetPoint { export interface CRSAlignResponse { binary_light_viewable?: string; + crs?: CRSAlignResponseCRS; +} + +export interface CRSAlignResponseCRS { + authority: string; + code: string; + name: string; } export interface CRSActiveParams { @@ -285,6 +328,7 @@ export interface SaveViewableFileParams { export interface SaveViewableFileResponse { binary_light_viewable?: string; collection_components?: SaveViewableFileResponseCollectionComponent[]; + crs?: SaveViewableFileResponseCRS; geode_id: string; geode_object_type: string; id: string; @@ -305,6 +349,12 @@ export interface SaveViewableFileResponseCollectionComponent { type: string; } +export interface SaveViewableFileResponseCRS { + authority: string; + code: string; + name: string; +} + export interface SaveViewableFileResponseMeshComponent { boundaries: string[]; geode_id: string; diff --git a/src/opengeodeweb_back/crs_functions.py b/src/opengeodeweb_back/crs_functions.py new file mode 100644 index 00000000..9ecd930d --- /dev/null +++ b/src/opengeodeweb_back/crs_functions.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +# Standard library imports +from typing import TYPE_CHECKING + +# Third party imports +import opengeode_geosciences as og_geosciences + +# Local application imports +from opengeodeweb_back.geode_objects.geode_brep import GeodeBRep +from opengeodeweb_back.geode_objects.geode_edged_curve2d import GeodeEdgedCurve2D +from opengeodeweb_back.geode_objects.geode_edged_curve3d import GeodeEdgedCurve3D +from opengeodeweb_back.geode_objects.geode_point_set2d import GeodePointSet2D +from opengeodeweb_back.geode_objects.geode_point_set3d import GeodePointSet3D +from opengeodeweb_back.geode_objects.geode_section import GeodeSection +from opengeodeweb_back.geode_objects.geode_solid_mesh3d import GeodeSolidMesh3D +from opengeodeweb_back.geode_objects.geode_surface_mesh2d import GeodeSurfaceMesh2D +from opengeodeweb_back.geode_objects.geode_surface_mesh3d import GeodeSurfaceMesh3D + +if TYPE_CHECKING: + import opengeode as og + + from opengeodeweb_back.geode_objects.geode_object import GeodeObject + +CRS_GEODE_OBJECTS = ( + GeodeBRep, + GeodeSection, + GeodePointSet2D, + GeodePointSet3D, + GeodeEdgedCurve2D, + GeodeEdgedCurve3D, + GeodeSurfaceMesh2D, + GeodeSurfaceMesh3D, + GeodeSolidMesh3D, +) + +CrsGeodeObject = ( + GeodeBRep + | GeodeSection + | GeodePointSet2D + | GeodePointSet3D + | GeodeEdgedCurve2D + | GeodeEdgedCurve3D + | GeodeSurfaceMesh2D + | GeodeSurfaceMesh3D + | GeodeSolidMesh3D +) + + +def geographic_info( + crs_manager: og.CoordinateReferenceSystemManager2D | og.CoordinateReferenceSystemManager3D, +) -> og_geosciences.GeographicCoordinateSystemInfo | None: + crs = crs_manager.active_coordinate_reference_system() + if isinstance( + crs, + og_geosciences.GeographicCoordinateSystem2D | og_geosciences.GeographicCoordinateSystem3D, + ): + return crs.info() + return None + + +def active_crs(geode_object: GeodeObject) -> dict[str, str] | None: + """{authority, code, name} of the active geographic CRS, None otherwise.""" + if not isinstance(geode_object, CRS_GEODE_OBJECTS): + return None + try: + info = geographic_info(geode_object.crs_manager()) + except ValueError: # A model without any component mesh has no CRS + return None + if info is None: + return None + return {"authority": info.authority, "code": info.code, "name": info.name} diff --git a/src/opengeodeweb_back/routes/create/schemas/edged_curve.json b/src/opengeodeweb_back/routes/create/schemas/edged_curve.json index 6e050553..3e4e4853 100644 --- a/src/opengeodeweb_back/routes/create/schemas/edged_curve.json +++ b/src/opengeodeweb_back/routes/create/schemas/edged_curve.json @@ -178,6 +178,26 @@ "type": "string", "minLength": 36, "maxLength": 36 + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [ @@ -191,4 +211,4 @@ ], "additionalProperties": false } -} \ No newline at end of file +} diff --git a/src/opengeodeweb_back/routes/create/schemas/edged_curve.py b/src/opengeodeweb_back/routes/create/schemas/edged_curve.py index 5376ba91..3716d448 100644 --- a/src/opengeodeweb_back/routes/create/schemas/edged_curve.py +++ b/src/opengeodeweb_back/routes/create/schemas/edged_curve.py @@ -39,6 +39,16 @@ def __post_init__(self) -> None: type: str +@dataclass +class CRS(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + name: str + + @dataclass class MeshComponent(DataClassJsonMixin): def __post_init__(self) -> None: @@ -67,6 +77,7 @@ def __post_init__(self) -> None: viewer_type: str binary_light_viewable: Optional[str] = None collection_components: Optional[List[CollectionComponent]] = None + crs: Optional[CRS] = None mesh_components: Optional[List[MeshComponent]] = None nb_vertices: Optional[int] = None viewable_file: Optional[str] = None diff --git a/src/opengeodeweb_back/routes/create/schemas/point_set.json b/src/opengeodeweb_back/routes/create/schemas/point_set.json index 04b66ba5..b339b2ad 100644 --- a/src/opengeodeweb_back/routes/create/schemas/point_set.json +++ b/src/opengeodeweb_back/routes/create/schemas/point_set.json @@ -164,6 +164,26 @@ "type": "string", "minLength": 36, "maxLength": 36 + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [ @@ -177,4 +197,4 @@ ], "additionalProperties": false } -} \ No newline at end of file +} diff --git a/src/opengeodeweb_back/routes/create/schemas/point_set.py b/src/opengeodeweb_back/routes/create/schemas/point_set.py index 1b18fa75..644cfb14 100644 --- a/src/opengeodeweb_back/routes/create/schemas/point_set.py +++ b/src/opengeodeweb_back/routes/create/schemas/point_set.py @@ -38,6 +38,16 @@ def __post_init__(self) -> None: type: str +@dataclass +class CRS(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + name: str + + @dataclass class MeshComponent(DataClassJsonMixin): def __post_init__(self) -> None: @@ -66,6 +76,7 @@ def __post_init__(self) -> None: viewer_type: str binary_light_viewable: Optional[str] = None collection_components: Optional[List[CollectionComponent]] = None + crs: Optional[CRS] = None mesh_components: Optional[List[MeshComponent]] = None nb_vertices: Optional[int] = None viewable_file: Optional[str] = None diff --git a/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.json b/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.json index 187cf17e..b91103af 100644 --- a/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.json +++ b/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.json @@ -177,6 +177,26 @@ "type": "string", "minLength": 36, "maxLength": 36 + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [ @@ -190,4 +210,4 @@ ], "additionalProperties": false } -} \ No newline at end of file +} diff --git a/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.py b/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.py index 937ba263..ffeb97b4 100644 --- a/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.py +++ b/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.py @@ -39,6 +39,16 @@ def __post_init__(self) -> None: type: str +@dataclass +class CRS(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + name: str + + @dataclass class MeshComponent(DataClassJsonMixin): def __post_init__(self) -> None: @@ -67,6 +77,7 @@ def __post_init__(self) -> None: viewer_type: str binary_light_viewable: Optional[str] = None collection_components: Optional[List[CollectionComponent]] = None + crs: Optional[CRS] = None mesh_components: Optional[List[MeshComponent]] = None nb_vertices: Optional[int] = None viewable_file: Optional[str] = None diff --git a/src/opengeodeweb_back/routes/crs/blueprint_crs.py b/src/opengeodeweb_back/routes/crs/blueprint_crs.py index e0d87f05..6c5d817a 100644 --- a/src/opengeodeweb_back/routes/crs/blueprint_crs.py +++ b/src/opengeodeweb_back/routes/crs/blueprint_crs.py @@ -1,4 +1,5 @@ # Standard library imports +from typing import Any # Third party imports import flask @@ -7,47 +8,23 @@ # Local application imports from opengeodeweb_back import geode_functions, utils_functions -from opengeodeweb_back.geode_objects.geode_brep import GeodeBRep -from opengeodeweb_back.geode_objects.geode_edged_curve2d import GeodeEdgedCurve2D -from opengeodeweb_back.geode_objects.geode_edged_curve3d import GeodeEdgedCurve3D -from opengeodeweb_back.geode_objects.geode_point_set2d import GeodePointSet2D -from opengeodeweb_back.geode_objects.geode_point_set3d import GeodePointSet3D -from opengeodeweb_back.geode_objects.geode_section import GeodeSection -from opengeodeweb_back.geode_objects.geode_solid_mesh3d import GeodeSolidMesh3D -from opengeodeweb_back.geode_objects.geode_surface_mesh2d import GeodeSurfaceMesh2D -from opengeodeweb_back.geode_objects.geode_surface_mesh3d import GeodeSurfaceMesh3D +from opengeodeweb_back.crs_functions import ( + CRS_GEODE_OBJECTS, + CrsGeodeObject, + geographic_info, +) from opengeodeweb_back.routes.crs import schemas from opengeodeweb_back.typed_route import typed_route routes = flask.Blueprint("crs", __name__, url_prefix="/crs") -CRS_GEODE_OBJECTS = ( - GeodeBRep, - GeodeSection, - GeodePointSet2D, - GeodePointSet3D, - GeodeEdgedCurve2D, - GeodeEdgedCurve3D, - GeodeSurfaceMesh2D, - GeodeSurfaceMesh3D, - GeodeSolidMesh3D, -) - def _prepare_crs_name( - geode_object: GeodeBRep - | GeodeSection - | GeodePointSet2D - | GeodePointSet3D - | GeodeEdgedCurve2D - | GeodeEdgedCurve3D - | GeodeSurfaceMesh2D - | GeodeSurfaceMesh3D - | GeodeSolidMesh3D, + geode_object: CrsGeodeObject, crs_name: str, ) -> None: crs_manager = geode_object.crs_manager() - info = _geographic_info(crs_manager) + info = geographic_info(crs_manager) if info is not None and info.authority_code() == crs_name: flask.abort(400, f"{crs_name} is already the active CRS") # A registered inactive CRS may hold coordinates derived from an outdated label: recompute it @@ -55,16 +32,9 @@ def _prepare_crs_name( geode_object.delete_crs(crs_name) -def _geographic_info( - crs_manager: og.CoordinateReferenceSystemManager2D | og.CoordinateReferenceSystemManager3D, -) -> og_geosciences.GeographicCoordinateSystemInfo | None: - crs = crs_manager.active_coordinate_reference_system() - if isinstance( - crs, - og_geosciences.GeographicCoordinateSystem2D | og_geosciences.GeographicCoordinateSystem3D, - ): - return crs.info() - return None +def _crs_response(saved: dict[str, Any]) -> dict[str, Any]: + # Only the fields the front refreshes after a CRS change + return {key: saved[key] for key in ("crs", "binary_light_viewable") if key in saved} @typed_route(routes, schemas.geographic_list_route) @@ -88,7 +58,7 @@ def active(params: schemas.Active) -> schemas.ActiveResponse: if not isinstance(geode_object, CRS_GEODE_OBJECTS): flask.abort(400, f"{params.id} does not support coordinate reference systems") crs_manager = geode_object.crs_manager() - info = _geographic_info(crs_manager) + info = geographic_info(crs_manager) crs_info = ( None if info is None @@ -111,10 +81,10 @@ def assign_geographic(params: schemas.AssignGeographic) -> schemas.AssignGeograp info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) _prepare_crs_name(geode_object, info.authority_code()) geode_object.assign_crs(info.authority_code(), info) - utils_functions.save_all_viewables_and_return_info( + saved = utils_functions.save_all_viewables_and_return_info( geode_object, data, geode_functions.data_file_path(params.id) ) - return schemas.AssignGeographicResponse() + return schemas.AssignGeographicResponse.from_dict(_crs_response(saved)) @typed_route(routes, schemas.convert_geographic_route) @@ -126,7 +96,7 @@ def convert_geographic(params: schemas.ConvertGeographic) -> schemas.ConvertGeog if not isinstance(geode_object, CRS_GEODE_OBJECTS): flask.abort(400, f"{params.id} does not support coordinate reference systems") crs_manager = geode_object.crs_manager() - if _geographic_info(crs_manager) is None: + if geographic_info(crs_manager) is None: flask.abort(400, "Assign a geographic CRS before converting") info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) _prepare_crs_name(geode_object, info.authority_code()) @@ -138,9 +108,7 @@ def convert_geographic(params: schemas.ConvertGeographic) -> schemas.ConvertGeog geode_object, data, geode_functions.data_file_path(params.id) ) # The front rebuilds its local actor from the new light viewable - return schemas.ConvertGeographicResponse( - binary_light_viewable=saved.get("binary_light_viewable") - ) + return schemas.ConvertGeographicResponse.from_dict(_crs_response(saved)) def _coordinate_system( @@ -158,7 +126,7 @@ def align(params: schemas.Align) -> schemas.AlignResponse: ) if not isinstance(geode_object, CRS_GEODE_OBJECTS): flask.abort(400, f"{params.id} does not support coordinate reference systems") - if _geographic_info(geode_object.crs_manager()) is not None: + if geographic_info(geode_object.crs_manager()) is not None: flask.abort(400, "Data already has a geographic CRS, use convert") try: source = _coordinate_system(params.source_points) @@ -172,4 +140,4 @@ def align(params: schemas.Align) -> schemas.AlignResponse: geode_object, data, geode_functions.data_file_path(params.id) ) # The front rebuilds its local actor from the new light viewable - return schemas.AlignResponse(binary_light_viewable=saved.get("binary_light_viewable")) + return schemas.AlignResponse.from_dict(_crs_response(saved)) diff --git a/src/opengeodeweb_back/routes/crs/schemas/align.json b/src/opengeodeweb_back/routes/crs/schemas/align.json index bbe3e8bd..1fc0113c 100644 --- a/src/opengeodeweb_back/routes/crs/schemas/align.json +++ b/src/opengeodeweb_back/routes/crs/schemas/align.json @@ -79,6 +79,26 @@ "properties": { "binary_light_viewable": { "type": "string" + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [], diff --git a/src/opengeodeweb_back/routes/crs/schemas/align.py b/src/opengeodeweb_back/routes/crs/schemas/align.py index 89bb6e1a..9d5a47d1 100644 --- a/src/opengeodeweb_back/routes/crs/schemas/align.py +++ b/src/opengeodeweb_back/routes/crs/schemas/align.py @@ -38,12 +38,23 @@ def __post_init__(self) -> None: target_points: List[TargetPoint] +@dataclass +class CRS(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + name: str + + @dataclass class AlignResponse(DataClassJsonMixin): def __post_init__(self) -> None: print_dataclass(self) binary_light_viewable: Optional[str] = None + crs: Optional[CRS] = None align_route = Route( diff --git a/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.json b/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.json index 4052b452..1b3059e8 100644 --- a/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.json +++ b/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.json @@ -32,7 +32,28 @@ "additionalProperties": false, "response": { "type": "object", - "properties": {}, + "properties": { + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false + } + }, "required": [], "additionalProperties": false } diff --git a/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.py b/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.py index 42b5590f..c8ed1204 100644 --- a/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.py +++ b/src/opengeodeweb_back/routes/crs/schemas/assign_geographic.py @@ -1,5 +1,6 @@ from pathlib import Path from opengeodeweb_microservice.schemas import Route, load_schema +from typing import Optional from dataclasses_json import DataClassJsonMixin from opengeodeweb_microservice.schemas import print_dataclass from dataclasses import dataclass @@ -16,12 +17,22 @@ def __post_init__(self) -> None: name: str +@dataclass +class CRS(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + name: str + + @dataclass class AssignGeographicResponse(DataClassJsonMixin): def __post_init__(self) -> None: print_dataclass(self) - pass + crs: Optional[CRS] = None assign_geographic_route = Route( diff --git a/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.json b/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.json index 3113fdf7..07c77e6e 100644 --- a/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.json +++ b/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.json @@ -35,6 +35,26 @@ "properties": { "binary_light_viewable": { "type": "string" + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [], diff --git a/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.py b/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.py index 280ac867..59311161 100644 --- a/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.py +++ b/src/opengeodeweb_back/routes/crs/schemas/convert_geographic.py @@ -17,12 +17,23 @@ def __post_init__(self) -> None: name: str +@dataclass +class CRS(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + name: str + + @dataclass class ConvertGeographicResponse(DataClassJsonMixin): def __post_init__(self) -> None: print_dataclass(self) binary_light_viewable: Optional[str] = None + crs: Optional[CRS] = None convert_geographic_route = Route( diff --git a/src/opengeodeweb_back/routes/schemas/save_viewable_file.json b/src/opengeodeweb_back/routes/schemas/save_viewable_file.json index c3335d36..cacf6e8b 100644 --- a/src/opengeodeweb_back/routes/schemas/save_viewable_file.json +++ b/src/opengeodeweb_back/routes/schemas/save_viewable_file.json @@ -144,6 +144,26 @@ "type": "string", "minLength": 36, "maxLength": 36 + }, + "crs": { + "type": "object", + "properties": { + "authority": { + "type": "string" + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "authority", + "code", + "name" + ], + "additionalProperties": false } }, "required": [ @@ -157,4 +177,4 @@ ], "additionalProperties": false } -} \ No newline at end of file +} diff --git a/src/opengeodeweb_back/routes/schemas/save_viewable_file.py b/src/opengeodeweb_back/routes/schemas/save_viewable_file.py index 54189b6c..1ff3c353 100644 --- a/src/opengeodeweb_back/routes/schemas/save_viewable_file.py +++ b/src/opengeodeweb_back/routes/schemas/save_viewable_file.py @@ -27,6 +27,16 @@ def __post_init__(self) -> None: type: str +@dataclass +class CRS(DataClassJsonMixin): + def __post_init__(self) -> None: + print_dataclass(self) + + authority: str + code: str + name: str + + @dataclass class MeshComponent(DataClassJsonMixin): def __post_init__(self) -> None: @@ -55,6 +65,7 @@ def __post_init__(self) -> None: viewer_type: str binary_light_viewable: Optional[str] = None collection_components: Optional[List[CollectionComponent]] = None + crs: Optional[CRS] = None mesh_components: Optional[List[MeshComponent]] = None nb_vertices: Optional[int] = None viewable_file: Optional[str] = None diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index 5f4c1287..1870a3e5 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -25,6 +25,7 @@ # Local application imports from . import geode_functions +from .crs_functions import active_crs from .geode_objects import geode_objects from .geode_objects.geode_model import GeodeModel from .geode_objects.geode_vertex_set import GeodeVertexSet @@ -344,6 +345,9 @@ def save_all_viewables_and_return_info( response["nb_vertices"] = geode_object.vertex_set.nb_vertices() if isinstance(geode_object, GeodeModel): response |= model_components(data.id, geode_object, data.viewable_file) + crs = active_crs(geode_object) + if crs is not None: + response["crs"] = crs return response diff --git a/tests/test_crs_routes.py b/tests/test_crs_routes.py index b47c5b14..89fcd3c9 100644 --- a/tests/test_crs_routes.py +++ b/tests/test_crs_routes.py @@ -1,12 +1,16 @@ from __future__ import annotations # Standard library imports +import shutil +from pathlib import Path from typing import TYPE_CHECKING # Third party imports import pytest # Local application imports +from opengeodeweb_microservice.database.data import Data + from opengeodeweb_back import geode_functions, test_utils from opengeodeweb_back.geode_objects.geode_point_set3d import GeodePointSet3D @@ -65,6 +69,7 @@ def get_full_data() -> test_utils.JsonData: response = client.post(route, json=get_full_data()) assert response.status_code == 200 + assert response.get_json()["crs"] == LAMBERT response = client.post(route, json={"id": point_set_id, **UTM}) assert response.status_code == 200 response = client.post(active_route, json={"id": point_set_id}) @@ -122,6 +127,7 @@ def get_full_data() -> test_utils.JsonData: assert response.status_code == 200 # The front rebuilds its local actor from the new light viewable assert type(response.get_json()["binary_light_viewable"]) is str + assert response.get_json()["crs"] == UTM response = client.post(active_route, json={"id": point_set_id}) assert response.get_json() == {"crs_name": "EPSG:32631", "crs_info": UTM} assert _first_point(client, point_set_id) != lambert_point @@ -193,6 +199,7 @@ def get_full_data() -> test_utils.JsonData: response = client.post(route, json=get_full_data()) assert response.status_code == 200 assert type(response.get_json()["binary_light_viewable"]) is str + assert response.get_json()["crs"] == LAMBERT response = client.post("/opengeodeweb_back/crs/active", json={"id": point_set_id}) assert response.get_json() == {"crs_name": "EPSG:2154", "crs_info": LAMBERT} assert _first_point(client, point_set_id) == [1000, 2000, 5] @@ -261,3 +268,28 @@ def test_convert_out_of_domain(client: FlaskClient) -> None: client.post("/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **WGS84}) response = client.post(convert_route, json={"id": point_set_id, **LAMBERT}) assert response.status_code == 400 + + +def test_saved_native_file_keeps_its_crs(client: FlaskClient) -> None: + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + client.post("/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **LAMBERT}) + with client.application.app_context(): + data = Data.get(point_set_id) + assert data is not None + native_path = Path(geode_functions.data_file_path(point_set_id, data.native_file)) + upload_path = Path(geode_functions.upload_file_path("georeferenced.og_pts3d")) + upload_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy(native_path, upload_path) + try: + response = client.post( + "/opengeodeweb_back/save_viewable_file", + json={"geode_object_type": "PointSet3D", "filename": "georeferenced.og_pts3d"}, + ) + finally: + upload_path.unlink() + assert response.status_code == 200 + assert response.get_json()["crs"] == LAMBERT + # The copy lives in the shared test data folder: it must not outlive the test + assert not upload_path.exists() diff --git a/tests/test_routes.py b/tests/test_routes.py index aa35949e..46fa75a8 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -232,8 +232,9 @@ def get_full_data() -> test_utils.JsonData: test_utils.test_route_wrong_params(client, route, get_full_data) -def test_save_viewable_file(client: FlaskClient) -> TestResponse: - test_utils.check_save_viewable_file(client, "BRep", "corbi.og_brep") +def test_save_viewable_file(client: FlaskClient) -> None: + response = test_utils.check_save_viewable_file(client, "BRep", "corbi.og_brep") + assert "crs" not in response.get_json() def test_texture_coordinates(client: FlaskClient) -> None: From 9fc5284e5cc56a3965e6dbff0341e3ed3a2b845b Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Fri, 23 Oct 2026 09:38:16 +0000 Subject: [PATCH 08/10] Apply prepare changes --- opengeodeweb_back_typed_schemas.d.ts | 803 +++++++++++++-------------- 1 file changed, 374 insertions(+), 429 deletions(-) diff --git a/opengeodeweb_back_typed_schemas.d.ts b/opengeodeweb_back_typed_schemas.d.ts index d0b7c375..7c8bb273 100644 --- a/opengeodeweb_back_typed_schemas.d.ts +++ b/opengeodeweb_back_typed_schemas.d.ts @@ -2,668 +2,673 @@ import type json from "./opengeodeweb_back_schemas.json"; export interface CreatePolygonalSurfaceParams { - name: string; - points: PolygonalSurfacePoint[]; - polygons: Array; + name: string; + points: PolygonalSurfacePoint[]; + polygons: Array; } export interface PolygonalSurfacePoint { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreatePolygonalSurfaceResponse { - binary_light_viewable?: string; - collection_components?: CreatePolygonalSurfaceResponseCollectionComponent[]; - crs?: CreatePolygonalSurfaceResponseCRS; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreatePolygonalSurfaceResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreatePolygonalSurfaceResponseCollectionComponent[]; + crs?: CreatePolygonalSurfaceResponseCRS; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreatePolygonalSurfaceResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreatePolygonalSurfaceResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreatePolygonalSurfaceResponseCRS { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface CreatePolygonalSurfaceResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface CreatePointSetParams { - name: string; - points: Point[]; + name: string; + points: Point[]; } export interface Point { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreatePointSetResponse { - binary_light_viewable?: string; - collection_components?: CreatePointSetResponseCollectionComponent[]; - crs?: CreatePointSetResponseCRS; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreatePointSetResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreatePointSetResponseCollectionComponent[]; + crs?: CreatePointSetResponseCRS; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreatePointSetResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreatePointSetResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreatePointSetResponseCRS { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface CreatePointSetResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface CreateEdgedCurveParams { - edges: Array; - name: string; - points: EdgedCurvePoint[]; + edges: Array; + name: string; + points: EdgedCurvePoint[]; } export interface EdgedCurvePoint { - x: number; - y: number; - z: number; + x: number; + y: number; + z: number; } export interface CreateEdgedCurveResponse { - binary_light_viewable?: string; - collection_components?: CreateEdgedCurveResponseCollectionComponent[]; - crs?: CreateEdgedCurveResponseCRS; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: CreateEdgedCurveResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: CreateEdgedCurveResponseCollectionComponent[]; + crs?: CreateEdgedCurveResponseCRS; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: CreateEdgedCurveResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface CreateEdgedCurveResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface CreateEdgedCurveResponseCRS { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface CreateEdgedCurveResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface CRSGeographicListParams { - geode_object_type: string; + geode_object_type: string; } export interface CRSGeographicListResponse { - crs_list: CRSList[]; + crs_list: CRSList[]; } export interface CRSList { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface CRSConvertGeographicParams { - authority: string; - code: string; - id: string; - name: string; + authority: string; + code: string; + id: string; + name: string; } export interface CRSConvertGeographicResponse { - binary_light_viewable?: string; - crs?: CRSConvertGeographicResponseCRS; + binary_light_viewable?: string; + crs?: CRSConvertGeographicResponseCRS; } export interface CRSConvertGeographicResponseCRS { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface CRSAssignGeographicParams { - authority: string; - code: string; - id: string; - name: string; + authority: string; + code: string; + id: string; + name: string; } export interface CRSAssignGeographicResponse { - crs?: CRSAssignGeographicResponseCRS; + crs?: CRSAssignGeographicResponseCRS; } export interface CRSAssignGeographicResponseCRS { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface CRSAlignParams { - authority: string; - code: string; - id: string; - name: string; - source_points: SourcePoint[]; - target_points: TargetPoint[]; + authority: string; + code: string; + id: string; + name: string; + source_points: SourcePoint[]; + target_points: TargetPoint[]; } export interface SourcePoint { - x: number; - y: number; + x: number; + y: number; } export interface TargetPoint { - x: number; - y: number; + x: number; + y: number; } export interface CRSAlignResponse { - binary_light_viewable?: string; - crs?: CRSAlignResponseCRS; + binary_light_viewable?: string; + crs?: CRSAlignResponseCRS; } export interface CRSAlignResponseCRS { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface CRSActiveParams { - id: string; + id: string; } export interface CRSActiveResponse { - crs_info?: CRSInfo; - crs_name: string; + crs_info?: CRSInfo; + crs_name: string; } export interface CRSInfo { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface VertexAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface VertexAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface VertexAttributeNamesParams { - id: string; + id: string; } export interface VertexAttributeNamesResponse { - attributes: VertexAttributeNamesResponseAttribute[]; + attributes: VertexAttributeNamesResponseAttribute[]; } export interface VertexAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ValidateParams { - id: string; + id: string; } export interface ValidateResponse { - is_valid: boolean; - issues: string[]; - nb_issues: number; + is_valid: boolean; + issues: string[]; + nb_issues: number; } export interface UploadFileParams { - filename?: string; + filename?: string; } export interface UploadFileResponse { - message: string; + message: string; } export interface TimeSeriesMissingFilesParams { - filename: string; + filename: string; } export interface TimeSeriesMissingFilesResponse { - additional_files: string[]; - has_missing_files: boolean; - mandatory_files: string[]; + additional_files: string[]; + has_missing_files: boolean; + mandatory_files: string[]; } export interface TimeSeriesAllowedObjectsParams { - filename: string; + filename: string; } export interface TimeSeriesAllowedObjectsResponse { - allowed_objects: string[]; + allowed_objects: string[]; } export interface TextureCoordinatesParams { - id: string; + id: string; } export interface TextureCoordinatesResponse { - texture_coordinates: string[]; + texture_coordinates: string[]; } export interface SaveViewableFileParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface SaveViewableFileResponse { - binary_light_viewable?: string; - collection_components?: SaveViewableFileResponseCollectionComponent[]; - crs?: SaveViewableFileResponseCRS; - geode_id: string; - geode_object_type: string; - id: string; - is_viewable: boolean; - mesh_components?: SaveViewableFileResponseMeshComponent[]; - name: string; - native_file: string; - nb_vertices?: number; - viewable_file?: string; - viewer_type: string; + binary_light_viewable?: string; + collection_components?: SaveViewableFileResponseCollectionComponent[]; + crs?: SaveViewableFileResponseCRS; + geode_id: string; + geode_object_type: string; + id: string; + is_viewable: boolean; + mesh_components?: SaveViewableFileResponseMeshComponent[]; + name: string; + native_file: string; + nb_vertices?: number; + viewable_file?: string; + viewer_type: string; } export interface SaveViewableFileResponseCollectionComponent { - geode_id: string; - is_active: boolean; - items: string[]; - name: string; - type: string; + geode_id: string; + is_active: boolean; + items: string[]; + name: string; + type: string; } export interface SaveViewableFileResponseCRS { - authority: string; - code: string; - name: string; + authority: string; + code: string; + name: string; } export interface SaveViewableFileResponseMeshComponent { - boundaries: string[]; - geode_id: string; - internals: string[]; - is_active: boolean; - name: string; - type: string; - viewer_id: number; + boundaries: string[]; + geode_id: string; + internals: string[]; + is_active: boolean; + name: string; + type: string; + viewer_id: number; } export interface PolyhedronAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface PolyhedronAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface PolyhedronAttributeNamesParams { - id: string; + id: string; } export interface PolyhedronAttributeNamesResponse { - attributes: PolyhedronAttributeNamesResponseAttribute[]; + attributes: PolyhedronAttributeNamesResponseAttribute[]; } export interface PolyhedronAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface PolygonAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface PolygonAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface PolygonAttributeNamesParams { - id: string; + id: string; } export interface PolygonAttributeNamesResponse { - attributes: PolygonAttributeNamesResponseAttribute[]; + attributes: PolygonAttributeNamesResponseAttribute[]; } export interface PolygonAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } -export interface PingParams {} +export interface PingParams { +} export interface PingResponse { - message: string; + message: string; } export interface ModelComponentVertexAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentVertexAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentVertexAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentVertexAttributeNamesResponse { - attributes: ModelComponentVertexAttributeNamesResponseAttribute[]; + attributes: ModelComponentVertexAttributeNamesResponseAttribute[]; } export interface ModelComponentVertexAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentPolyhedronAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolyhedronAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentPolyhedronAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolyhedronAttributeNamesResponse { - attributes: ModelComponentPolyhedronAttributeNamesResponseAttribute[]; + attributes: ModelComponentPolyhedronAttributeNamesResponseAttribute[]; } export interface ModelComponentPolyhedronAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentPolygonAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolygonAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentPolygonAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentPolygonAttributeNamesResponse { - attributes: ModelComponentPolygonAttributeNamesResponseAttribute[]; + attributes: ModelComponentPolygonAttributeNamesResponseAttribute[]; } export interface ModelComponentPolygonAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ModelComponentEdgeAttributeRangeParams { - attribute_name: string; - component_ids: string[]; - id: string; + attribute_name: string; + component_ids: string[]; + id: string; } export interface ModelComponentEdgeAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface ModelComponentEdgeAttributeNamesParams { - component_ids: string[]; - id: string; + component_ids: string[]; + id: string; } export interface ModelComponentEdgeAttributeNamesResponse { - attributes: ModelComponentEdgeAttributeNamesResponseAttribute[]; + attributes: ModelComponentEdgeAttributeNamesResponseAttribute[]; } export interface ModelComponentEdgeAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } -export interface KillParams {} +export interface KillParams { +} export interface KillResponse { - message: string; + message: string; } -export interface ImportProjectParams {} +export interface ImportProjectParams { +} export interface ImportProjectResponse { - snapshot: { [key: string]: any }; + snapshot: { [key: string]: any }; } export interface GeodeObjectsAndOutputExtensionsParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface GeodeObjectsAndOutputExtensionsResponse { - geode_objects_and_output_extensions: { [key: string]: { [key: string]: boolean } }; + geode_objects_and_output_extensions: { [key: string]: { [key: string]: boolean } }; } export interface GeodeObjectInheritanceParams { - geode_object_type: string; + geode_object_type: string; } export interface GeodeObjectInheritanceResponse { - children: string[]; - parents: string[]; + children: string[]; + parents: string[]; } export interface ExportProjectParams { - filename: string; - snapshot: { [key: string]: any }; + filename: string; + snapshot: { [key: string]: any }; } export interface EdgeAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface EdgeAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface EdgeAttributeNamesParams { - id: string; + id: string; } export interface EdgeAttributeNamesResponse { - attributes: EdgeAttributeNamesResponseAttribute[]; + attributes: EdgeAttributeNamesResponseAttribute[]; } export interface EdgeAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface DataMissingFilesParams { - filename: string; - geode_object_type: string; + filename: string; + geode_object_type: string; } export interface DataMissingFilesResponse { - additional_files: string[]; - has_missing_files: boolean; - mandatory_files: string[]; + additional_files: string[]; + has_missing_files: boolean; + mandatory_files: string[]; } export interface CellAttributeRangeParams { - attribute_name: string; - id: string; + attribute_name: string; + id: string; } export interface CellAttributeRangeResponse { - max_values: number[]; - min_values: number[]; - no_data: boolean; + max_values: number[]; + min_values: number[]; + no_data: boolean; } export interface CellAttributeNamesParams { - id: string; + id: string; } export interface CellAttributeNamesResponse { - attributes: CellAttributeNamesResponseAttribute[]; + attributes: CellAttributeNamesResponseAttribute[]; } export interface CellAttributeNamesResponseAttribute { - attribute_id: string; - attribute_name: string; - nb_items: number; - time_steps: number[]; + attribute_id: string; + attribute_name: string; + nb_items: number; + time_steps: number[]; } export interface ApplyTimeSeriesParams { - filename: string; - id: string; + filename: string; + id: string; } -export interface ApplyTimeSeriesResponse {} +export interface ApplyTimeSeriesResponse { +} export interface AllowedObjectsParams { - filename: string; + filename: string; } export interface AllowedObjectsResponse { - allowed_objects: { [key: string]: AllowedObject }; + allowed_objects: { [key: string]: AllowedObject }; } export interface AllowedObject { - is_loadable: number; - object_priority: number; + is_loadable: number; + object_priority: number; } -export interface AllowedFilesParams {} +export interface AllowedFilesParams { +} export interface AllowedFilesResponse { - data_extensions: string[]; - time_series_extensions: string[]; + data_extensions: string[]; + time_series_extensions: string[]; } export interface ErrorResponse { - code: number; - description: string; - name: string; + code: number; + description: string; + name: string; } // `__params` and `__response` only exist at type level, to infer request/response types from a schema. @@ -676,111 +681,51 @@ export type TypedSchema = { export interface Schemas { readonly opengeodeweb_back: { readonly create: { - readonly polygonal_surface: (typeof json)["opengeodeweb_back"]["create"]["polygonal_surface"] & - TypedSchema; - readonly point_set: (typeof json)["opengeodeweb_back"]["create"]["point_set"] & - TypedSchema; - readonly edged_curve: (typeof json)["opengeodeweb_back"]["create"]["edged_curve"] & - TypedSchema; + readonly polygonal_surface: (typeof json)["opengeodeweb_back"]["create"]["polygonal_surface"] & TypedSchema; + readonly point_set: (typeof json)["opengeodeweb_back"]["create"]["point_set"] & TypedSchema; + readonly edged_curve: (typeof json)["opengeodeweb_back"]["create"]["edged_curve"] & TypedSchema; }; readonly crs: { - readonly geographic_list: (typeof json)["opengeodeweb_back"]["crs"]["geographic_list"] & - TypedSchema; - readonly convert_geographic: (typeof json)["opengeodeweb_back"]["crs"]["convert_geographic"] & - TypedSchema; - readonly assign_geographic: (typeof json)["opengeodeweb_back"]["crs"]["assign_geographic"] & - TypedSchema; - readonly align: (typeof json)["opengeodeweb_back"]["crs"]["align"] & - TypedSchema; - readonly active: (typeof json)["opengeodeweb_back"]["crs"]["active"] & - TypedSchema; + readonly geographic_list: (typeof json)["opengeodeweb_back"]["crs"]["geographic_list"] & TypedSchema; + readonly convert_geographic: (typeof json)["opengeodeweb_back"]["crs"]["convert_geographic"] & TypedSchema; + readonly assign_geographic: (typeof json)["opengeodeweb_back"]["crs"]["assign_geographic"] & TypedSchema; + readonly align: (typeof json)["opengeodeweb_back"]["crs"]["align"] & TypedSchema; + readonly active: (typeof json)["opengeodeweb_back"]["crs"]["active"] & TypedSchema; }; - readonly vertex_attribute_range: (typeof json)["opengeodeweb_back"]["vertex_attribute_range"] & - TypedSchema; - readonly vertex_attribute_names: (typeof json)["opengeodeweb_back"]["vertex_attribute_names"] & - TypedSchema; - readonly validate: (typeof json)["opengeodeweb_back"]["validate"] & - TypedSchema; - readonly upload_file: (typeof json)["opengeodeweb_back"]["upload_file"] & - TypedSchema; - readonly time_series_missing_files: (typeof json)["opengeodeweb_back"]["time_series_missing_files"] & - TypedSchema; - readonly time_series_allowed_objects: (typeof json)["opengeodeweb_back"]["time_series_allowed_objects"] & - TypedSchema; - readonly texture_coordinates: (typeof json)["opengeodeweb_back"]["texture_coordinates"] & - TypedSchema; - readonly save_viewable_file: (typeof json)["opengeodeweb_back"]["save_viewable_file"] & - TypedSchema; - readonly polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_range"] & - TypedSchema; - readonly polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_names"] & - TypedSchema; - readonly polygon_attribute_range: (typeof json)["opengeodeweb_back"]["polygon_attribute_range"] & - TypedSchema; - readonly polygon_attribute_names: (typeof json)["opengeodeweb_back"]["polygon_attribute_names"] & - TypedSchema; - readonly ping: (typeof json)["opengeodeweb_back"]["ping"] & - TypedSchema; - readonly model_component_vertex_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_range"] & - TypedSchema< - ModelComponentVertexAttributeRangeParams, - ModelComponentVertexAttributeRangeResponse - >; - readonly model_component_vertex_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_names"] & - TypedSchema< - ModelComponentVertexAttributeNamesParams, - ModelComponentVertexAttributeNamesResponse - >; - readonly model_component_polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_range"] & - TypedSchema< - ModelComponentPolyhedronAttributeRangeParams, - ModelComponentPolyhedronAttributeRangeResponse - >; - readonly model_component_polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_names"] & - TypedSchema< - ModelComponentPolyhedronAttributeNamesParams, - ModelComponentPolyhedronAttributeNamesResponse - >; - readonly model_component_polygon_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_range"] & - TypedSchema< - ModelComponentPolygonAttributeRangeParams, - ModelComponentPolygonAttributeRangeResponse - >; - readonly model_component_polygon_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_names"] & - TypedSchema< - ModelComponentPolygonAttributeNamesParams, - ModelComponentPolygonAttributeNamesResponse - >; - readonly model_component_edge_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_range"] & - TypedSchema; - readonly model_component_edge_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_names"] & - TypedSchema; - readonly kill: (typeof json)["opengeodeweb_back"]["kill"] & - TypedSchema; - readonly import_project: (typeof json)["opengeodeweb_back"]["import_project"] & - TypedSchema; - readonly geode_objects_and_output_extensions: (typeof json)["opengeodeweb_back"]["geode_objects_and_output_extensions"] & - TypedSchema; - readonly geode_object_inheritance: (typeof json)["opengeodeweb_back"]["geode_object_inheritance"] & - TypedSchema; - readonly export_project: (typeof json)["opengeodeweb_back"]["export_project"] & - TypedSchema; - readonly edge_attribute_range: (typeof json)["opengeodeweb_back"]["edge_attribute_range"] & - TypedSchema; - readonly edge_attribute_names: (typeof json)["opengeodeweb_back"]["edge_attribute_names"] & - TypedSchema; - readonly data_missing_files: (typeof json)["opengeodeweb_back"]["data_missing_files"] & - TypedSchema; - readonly cell_attribute_range: (typeof json)["opengeodeweb_back"]["cell_attribute_range"] & - TypedSchema; - readonly cell_attribute_names: (typeof json)["opengeodeweb_back"]["cell_attribute_names"] & - TypedSchema; - readonly apply_time_series: (typeof json)["opengeodeweb_back"]["apply_time_series"] & - TypedSchema; - readonly allowed_objects: (typeof json)["opengeodeweb_back"]["allowed_objects"] & - TypedSchema; - readonly allowed_files: (typeof json)["opengeodeweb_back"]["allowed_files"] & - TypedSchema; + readonly vertex_attribute_range: (typeof json)["opengeodeweb_back"]["vertex_attribute_range"] & TypedSchema; + readonly vertex_attribute_names: (typeof json)["opengeodeweb_back"]["vertex_attribute_names"] & TypedSchema; + readonly validate: (typeof json)["opengeodeweb_back"]["validate"] & TypedSchema; + readonly upload_file: (typeof json)["opengeodeweb_back"]["upload_file"] & TypedSchema; + readonly time_series_missing_files: (typeof json)["opengeodeweb_back"]["time_series_missing_files"] & TypedSchema; + readonly time_series_allowed_objects: (typeof json)["opengeodeweb_back"]["time_series_allowed_objects"] & TypedSchema; + readonly texture_coordinates: (typeof json)["opengeodeweb_back"]["texture_coordinates"] & TypedSchema; + readonly save_viewable_file: (typeof json)["opengeodeweb_back"]["save_viewable_file"] & TypedSchema; + readonly polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_range"] & TypedSchema; + readonly polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["polyhedron_attribute_names"] & TypedSchema; + readonly polygon_attribute_range: (typeof json)["opengeodeweb_back"]["polygon_attribute_range"] & TypedSchema; + readonly polygon_attribute_names: (typeof json)["opengeodeweb_back"]["polygon_attribute_names"] & TypedSchema; + readonly ping: (typeof json)["opengeodeweb_back"]["ping"] & TypedSchema; + readonly model_component_vertex_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_range"] & TypedSchema; + readonly model_component_vertex_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_vertex_attribute_names"] & TypedSchema; + readonly model_component_polyhedron_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_range"] & TypedSchema; + readonly model_component_polyhedron_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polyhedron_attribute_names"] & TypedSchema; + readonly model_component_polygon_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_range"] & TypedSchema; + readonly model_component_polygon_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_polygon_attribute_names"] & TypedSchema; + readonly model_component_edge_attribute_range: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_range"] & TypedSchema; + readonly model_component_edge_attribute_names: (typeof json)["opengeodeweb_back"]["model_component_edge_attribute_names"] & TypedSchema; + readonly kill: (typeof json)["opengeodeweb_back"]["kill"] & TypedSchema; + readonly import_project: (typeof json)["opengeodeweb_back"]["import_project"] & TypedSchema; + readonly geode_objects_and_output_extensions: (typeof json)["opengeodeweb_back"]["geode_objects_and_output_extensions"] & TypedSchema; + readonly geode_object_inheritance: (typeof json)["opengeodeweb_back"]["geode_object_inheritance"] & TypedSchema; + readonly export_project: (typeof json)["opengeodeweb_back"]["export_project"] & TypedSchema; + readonly edge_attribute_range: (typeof json)["opengeodeweb_back"]["edge_attribute_range"] & TypedSchema; + readonly edge_attribute_names: (typeof json)["opengeodeweb_back"]["edge_attribute_names"] & TypedSchema; + readonly data_missing_files: (typeof json)["opengeodeweb_back"]["data_missing_files"] & TypedSchema; + readonly cell_attribute_range: (typeof json)["opengeodeweb_back"]["cell_attribute_range"] & TypedSchema; + readonly cell_attribute_names: (typeof json)["opengeodeweb_back"]["cell_attribute_names"] & TypedSchema; + readonly apply_time_series: (typeof json)["opengeodeweb_back"]["apply_time_series"] & TypedSchema; + readonly allowed_objects: (typeof json)["opengeodeweb_back"]["allowed_objects"] & TypedSchema; + readonly allowed_files: (typeof json)["opengeodeweb_back"]["allowed_files"] & TypedSchema; }; } From 97593996240ca2bdcc14d73603fd538605802a48 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 23 Oct 2026 15:43:16 +0200 Subject: [PATCH 09/10] fixes --- src/opengeodeweb_back/crs_functions.py | 29 ++++++- .../geode_objects/crs_coordinates.py | 45 ++++++++++ .../geode_objects/geode_brep.py | 9 +- .../geode_objects/geode_edged_curve2d.py | 3 +- .../geode_objects/geode_edged_curve3d.py | 3 +- .../geode_objects/geode_point_set2d.py | 3 +- .../geode_objects/geode_point_set3d.py | 3 +- .../geode_objects/geode_section.py | 7 +- .../geode_objects/geode_solid_mesh3d.py | 3 +- .../geode_objects/geode_surface_mesh2d.py | 3 +- .../geode_objects/geode_surface_mesh3d.py | 3 +- .../routes/crs/blueprint_crs.py | 41 +++++---- tests/test_crs_routes.py | 85 ++++++++++++++++++- 13 files changed, 206 insertions(+), 31 deletions(-) create mode 100644 src/opengeodeweb_back/geode_objects/crs_coordinates.py diff --git a/src/opengeodeweb_back/crs_functions.py b/src/opengeodeweb_back/crs_functions.py index 9ecd930d..ef7ff216 100644 --- a/src/opengeodeweb_back/crs_functions.py +++ b/src/opengeodeweb_back/crs_functions.py @@ -1,6 +1,8 @@ from __future__ import annotations # Standard library imports +import functools +import logging from typing import TYPE_CHECKING # Third party imports @@ -22,6 +24,8 @@ from opengeodeweb_back.geode_objects.geode_object import GeodeObject +logger = logging.getLogger(__name__) + CRS_GEODE_OBJECTS = ( GeodeBRep, GeodeSection, @@ -47,6 +51,26 @@ ) +@functools.cache +def geographic_coordinate_systems( + is_3d: bool, # noqa: FBT001 positional so the cache key stays simple +) -> dict[str, og_geosciences.GeographicCoordinateSystemInfo]: + """Known geographic CRS by "AUTHORITY:CODE", built once: the list has thousands of entries.""" + infos = ( + og_geosciences.GeographicCoordinateSystem3D.geographic_coordinate_systems() + if is_3d + else og_geosciences.GeographicCoordinateSystem2D.geographic_coordinate_systems() + ) + return {info.authority_code(): info for info in infos} + + +def known_geographic_info( + geode_object: CrsGeodeObject, authority: str, code: str +) -> og_geosciences.GeographicCoordinateSystemInfo | None: + """The listed CRS for authority:code, None when PROJ does not know it.""" + return geographic_coordinate_systems(geode_object.is_3d()).get(f"{authority}:{code}") + + def geographic_info( crs_manager: og.CoordinateReferenceSystemManager2D | og.CoordinateReferenceSystemManager3D, ) -> og_geosciences.GeographicCoordinateSystemInfo | None: @@ -65,7 +89,10 @@ def active_crs(geode_object: GeodeObject) -> dict[str, str] | None: return None try: info = geographic_info(geode_object.crs_manager()) - except ValueError: # A model without any component mesh has no CRS + except (ValueError, RuntimeError) as error: + # The CRS is extra information: a model without component mesh, or a CRS the + # geosciences library cannot read, must not fail the whole data response + logger.warning("Cannot read the active CRS: %s", error) return None if info is None: return None diff --git a/src/opengeodeweb_back/geode_objects/crs_coordinates.py b/src/opengeodeweb_back/geode_objects/crs_coordinates.py new file mode 100644 index 00000000..0a4de673 --- /dev/null +++ b/src/opengeodeweb_back/geode_objects/crs_coordinates.py @@ -0,0 +1,45 @@ +# Standard library imports +from __future__ import annotations + +# Third party imports +import opengeode as og +import opengeode_geosciences as og_geosciences + +Mesh = ( + og.PointSet2D + | og.PointSet3D + | og.EdgedCurve2D + | og.EdgedCurve3D + | og.SurfaceMesh2D + | og.SurfaceMesh3D + | og.SolidMesh3D +) +CrsBuilder = ( + og.CoordinateReferenceSystemManagerBuilder2D | og.CoordinateReferenceSystemManagerBuilder3D +) + + +def _coordinates_name( + crs: og.CoordinateReferenceSystem2D | og.CoordinateReferenceSystem3D, +) -> str | None: + # A geographic CRS stores its coordinates in a vertex attribute named after the CRS + if isinstance( + crs, + og_geosciences.GeographicCoordinateSystem2D | og_geosciences.GeographicCoordinateSystem3D, + ): + return crs.info().name + return None + + +def delete_crs_with_coordinates(mesh: Mesh, crs_builder: CrsBuilder, crs_name: str) -> None: + """Delete an inactive CRS and its coordinates: each conversion stores new ones.""" + crs_manager = mesh.main_coordinate_reference_system_manager() + coordinates_name = _coordinates_name(crs_manager.find_coordinate_reference_system(crs_name)) + active_name = _coordinates_name(crs_manager.active_coordinate_reference_system()) + crs_builder.delete_coordinate_reference_system(crs_name) + # Attributes are only found by name: keep them when the active CRS shares it + if coordinates_name is None or coordinates_name == active_name: + return + attribute_manager = mesh.vertex_attribute_manager() + for attribute_id in attribute_manager.attribute_ids_matching_name(coordinates_name) or []: + attribute_manager.delete_attribute(attribute_id) diff --git a/src/opengeodeweb_back/geode_objects/geode_brep.py b/src/opengeodeweb_back/geode_objects/geode_brep.py index f6656c4e..db51e786 100644 --- a/src/opengeodeweb_back/geode_objects/geode_brep.py +++ b/src/opengeodeweb_back/geode_objects/geode_brep.py @@ -11,6 +11,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_model import ComponentRegistry, GeodeModel if TYPE_CHECKING: @@ -156,22 +157,22 @@ def delete_crs(self, crs_name: str) -> None: crs_builder = builder.corner_mesh_builder( corner ).main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(corner.mesh(), crs_builder, crs_name) for line in self.brep.lines(): crs_builder = builder.line_mesh_builder( line ).main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(line.mesh(), crs_builder, crs_name) for surface in self.brep.surfaces(): crs_builder = builder.surface_mesh_builder( surface ).main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(surface.mesh(), crs_builder, crs_name) for block in self.brep.blocks(): crs_builder = builder.block_mesh_builder( block ).main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(block.mesh(), crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py b/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py index 68406d27..8ecb1af1 100644 --- a/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py @@ -11,6 +11,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_graph import GeodeGraph if TYPE_CHECKING: @@ -108,7 +109,7 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: def delete_crs(self, crs_name: str) -> None: crs_builder = self.builder().main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(self.edged_curve, crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py b/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py index 9b7914d4..9e929ef4 100644 --- a/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py @@ -11,6 +11,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_graph import GeodeGraph if TYPE_CHECKING: @@ -108,7 +109,7 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: def delete_crs(self, crs_name: str) -> None: crs_builder = self.builder().main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(self.edged_curve, crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/geode_objects/geode_point_set2d.py b/src/opengeodeweb_back/geode_objects/geode_point_set2d.py index 96203af1..3f32d80c 100644 --- a/src/opengeodeweb_back/geode_objects/geode_point_set2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_point_set2d.py @@ -11,6 +11,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_vertex_set import GeodeVertexSet if TYPE_CHECKING: @@ -106,7 +107,7 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: def delete_crs(self, crs_name: str) -> None: crs_builder = self.builder().main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(self.point_set, crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/geode_objects/geode_point_set3d.py b/src/opengeodeweb_back/geode_objects/geode_point_set3d.py index 56820126..004261e0 100644 --- a/src/opengeodeweb_back/geode_objects/geode_point_set3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_point_set3d.py @@ -11,6 +11,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_vertex_set import GeodeVertexSet if TYPE_CHECKING: @@ -106,7 +107,7 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: def delete_crs(self, crs_name: str) -> None: crs_builder = self.builder().main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(self.point_set, crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/geode_objects/geode_section.py b/src/opengeodeweb_back/geode_objects/geode_section.py index 24d372f5..c684030b 100644 --- a/src/opengeodeweb_back/geode_objects/geode_section.py +++ b/src/opengeodeweb_back/geode_objects/geode_section.py @@ -11,6 +11,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_model import ComponentRegistry, GeodeModel if TYPE_CHECKING: @@ -144,17 +145,17 @@ def delete_crs(self, crs_name: str) -> None: crs_builder = builder.corner_mesh_builder( corner ).main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(corner.mesh(), crs_builder, crs_name) for line in self.section.lines(): crs_builder = builder.line_mesh_builder( line ).main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(line.mesh(), crs_builder, crs_name) for surface in self.section.surfaces(): crs_builder = builder.surface_mesh_builder( surface ).main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(surface.mesh(), crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py b/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py index eb872b21..d0bcba01 100644 --- a/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py @@ -9,6 +9,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_vertex_set import GeodeVertexSet if TYPE_CHECKING: @@ -54,7 +55,7 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: def delete_crs(self, crs_name: str) -> None: crs_builder = self.builder().main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(self.solid_mesh, crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py b/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py index 3f46bfd0..5c41d103 100644 --- a/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py +++ b/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py @@ -9,6 +9,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_vertex_set import GeodeVertexSet if TYPE_CHECKING: @@ -54,7 +55,7 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager2D: def delete_crs(self, crs_name: str) -> None: crs_builder = self.builder().main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(self.surface_mesh, crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py b/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py index 3fe48a77..95711b51 100644 --- a/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py +++ b/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py @@ -9,6 +9,7 @@ import opengeode_inspector as og_inspector # Local application imports +from .crs_coordinates import delete_crs_with_coordinates from .geode_vertex_set import GeodeVertexSet if TYPE_CHECKING: @@ -54,7 +55,7 @@ def crs_manager(self) -> og.CoordinateReferenceSystemManager3D: def delete_crs(self, crs_name: str) -> None: crs_builder = self.builder().main_coordinate_reference_system_manager_builder() - crs_builder.delete_coordinate_reference_system(crs_name) + delete_crs_with_coordinates(self.surface_mesh, crs_builder, crs_name) def assign_crs( self, crs_name: str, info: og_geosciences.GeographicCoordinateSystemInfo diff --git a/src/opengeodeweb_back/routes/crs/blueprint_crs.py b/src/opengeodeweb_back/routes/crs/blueprint_crs.py index 6c5d817a..895618e1 100644 --- a/src/opengeodeweb_back/routes/crs/blueprint_crs.py +++ b/src/opengeodeweb_back/routes/crs/blueprint_crs.py @@ -1,21 +1,28 @@ +from __future__ import annotations + # Standard library imports -from typing import Any +from typing import TYPE_CHECKING, Any # Third party imports import flask import opengeode as og -import opengeode_geosciences as og_geosciences # Local application imports from opengeodeweb_back import geode_functions, utils_functions from opengeodeweb_back.crs_functions import ( CRS_GEODE_OBJECTS, CrsGeodeObject, + active_crs, + geographic_coordinate_systems, geographic_info, + known_geographic_info, ) from opengeodeweb_back.routes.crs import schemas from opengeodeweb_back.typed_route import typed_route +if TYPE_CHECKING: + import opengeode_geosciences as og_geosciences + routes = flask.Blueprint("crs", __name__, url_prefix="/crs") @@ -32,6 +39,16 @@ def _prepare_crs_name( geode_object.delete_crs(crs_name) +def _known_info( + geode_object: CrsGeodeObject, authority: str, code: str +) -> og_geosciences.GeographicCoordinateSystemInfo: + # PROJ crashes the process on an unknown CRS: only accept listed ones, with their own name + info = known_geographic_info(geode_object, authority, code) + if info is None: + flask.abort(400, f"Unknown CRS {authority}:{code}") + return info + + def _crs_response(saved: dict[str, Any]) -> dict[str, Any]: # Only the fields the front refreshes after a CRS change return {key: saved[key] for key in ("crs", "binary_light_viewable") if key in saved} @@ -40,14 +57,9 @@ def _crs_response(saved: dict[str, Any]) -> dict[str, Any]: @typed_route(routes, schemas.geographic_list_route) def geographic_list(params: schemas.GeographicList) -> schemas.GeographicListResponse: geode_object = geode_functions.geode_object_from_string(params.geode_object_type) - infos = ( - og_geosciences.GeographicCoordinateSystem3D.geographic_coordinate_systems() - if geode_object.is_3d() - else og_geosciences.GeographicCoordinateSystem2D.geographic_coordinate_systems() - ) crs_list = [ schemas.geographic_list.CRSList(name=info.name, code=info.code, authority=info.authority) - for info in infos + for info in geographic_coordinate_systems(geode_object.is_3d()).values() ] return schemas.GeographicListResponse(crs_list=crs_list) @@ -78,13 +90,12 @@ def assign_geographic(params: schemas.AssignGeographic) -> schemas.AssignGeograp ) if not isinstance(geode_object, CRS_GEODE_OBJECTS): flask.abort(400, f"{params.id} does not support coordinate reference systems") - info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) + info = _known_info(geode_object, params.authority, params.code) _prepare_crs_name(geode_object, info.authority_code()) geode_object.assign_crs(info.authority_code(), info) - saved = utils_functions.save_all_viewables_and_return_info( - geode_object, data, geode_functions.data_file_path(params.id) - ) - return schemas.AssignGeographicResponse.from_dict(_crs_response(saved)) + # Assigning only relabels the coordinates: the viewables are unchanged + geode_object.save(geode_functions.data_file_path(params.id, data.native_file)) + return schemas.AssignGeographicResponse.from_dict({"crs": active_crs(geode_object)}) @typed_route(routes, schemas.convert_geographic_route) @@ -98,7 +109,7 @@ def convert_geographic(params: schemas.ConvertGeographic) -> schemas.ConvertGeog crs_manager = geode_object.crs_manager() if geographic_info(crs_manager) is None: flask.abort(400, "Assign a geographic CRS before converting") - info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) + info = _known_info(geode_object, params.authority, params.code) _prepare_crs_name(geode_object, info.authority_code()) try: geode_object.convert_crs(info.authority_code(), info) @@ -128,13 +139,13 @@ def align(params: schemas.Align) -> schemas.AlignResponse: flask.abort(400, f"{params.id} does not support coordinate reference systems") if geographic_info(geode_object.crs_manager()) is not None: flask.abort(400, "Data already has a geographic CRS, use convert") + info = _known_info(geode_object, params.authority, params.code) try: source = _coordinate_system(params.source_points) target = _coordinate_system(params.target_points) except RuntimeError: # OpenGeode cannot build a coordinate system from colinear points flask.abort(400, "Points must not be colinear") geode_object.create_crs("aligned", source, target) - info = og_geosciences.GeographicCoordinateSystemInfo(params.authority, params.code, params.name) geode_object.assign_crs(info.authority_code(), info) saved = utils_functions.save_all_viewables_and_return_info( geode_object, data, geode_functions.data_file_path(params.id) diff --git a/tests/test_crs_routes.py b/tests/test_crs_routes.py index 89fcd3c9..ab8d97ef 100644 --- a/tests/test_crs_routes.py +++ b/tests/test_crs_routes.py @@ -11,7 +11,7 @@ # Local application imports from opengeodeweb_microservice.database.data import Data -from opengeodeweb_back import geode_functions, test_utils +from opengeodeweb_back import crs_functions, geode_functions, test_utils from opengeodeweb_back.geode_objects.geode_point_set3d import GeodePointSet3D if TYPE_CHECKING: @@ -293,3 +293,86 @@ def test_saved_native_file_keeps_its_crs(client: FlaskClient) -> None: assert response.get_json()["crs"] == LAMBERT # The copy lives in the shared test data folder: it must not outlive the test assert not upload_path.exists() + + +def _attribute_count(client: FlaskClient, data_id: str) -> int: + with client.application.app_context(): + geode_object = geode_functions.load_geode_object(data_id) + assert isinstance(geode_object, GeodePointSet3D) + return len(geode_object.point_set.vertex_attribute_manager().attribute_ids()) + + +def test_round_trips_do_not_grow_the_native_file(client: FlaskClient) -> None: + convert_route = "/opengeodeweb_back/crs/convert_geographic" + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + client.post("/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **LAMBERT}) + client.post(convert_route, json={"id": point_set_id, **UTM}) + client.post(convert_route, json={"id": point_set_id, **LAMBERT}) + attribute_count = _attribute_count(client, point_set_id) + + for _ in range(2): + client.post(convert_route, json={"id": point_set_id, **UTM}) + client.post(convert_route, json={"id": point_set_id, **LAMBERT}) + assert _attribute_count(client, point_set_id) == attribute_count + + +def test_unknown_crs(client: FlaskClient) -> None: + unknown = {"authority": "FOO", "code": "1", "name": "Unknown"} + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + + response = client.post( + "/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **unknown} + ) + assert response.status_code == 400 + response = client.post( + "/opengeodeweb_back/crs/align", + json={ + "id": _create_point_set(client), + "source_points": SOURCE, + "target_points": TARGET, + **unknown, + }, + ) + assert response.status_code == 400 + + # The name comes from the CRS list, not from the request + response = client.post( + "/opengeodeweb_back/crs/assign_geographic", + json={"id": point_set_id, **LAMBERT, "name": "Wrong name"}, + ) + assert response.get_json()["crs"] == LAMBERT + response = client.post( + "/opengeodeweb_back/crs/convert_geographic", json={"id": point_set_id, **unknown} + ) + assert response.status_code == 400 + + +def test_assign_only_rewrites_the_native_file(client: FlaskClient) -> None: + point_set_id = test_utils.check_save_viewable_file( + client, "PointSet3D", "test.og_pts3d" + ).get_json()["id"] + with client.application.app_context(): + data = Data.get(point_set_id) + assert data is not None + assert data.viewable_file is not None + viewable_path = Path(geode_functions.data_file_path(point_set_id, data.viewable_file)) + viewable_mtime = viewable_path.stat().st_mtime_ns + + response = client.post( + "/opengeodeweb_back/crs/assign_geographic", json={"id": point_set_id, **LAMBERT} + ) + assert response.status_code == 200 + assert viewable_path.stat().st_mtime_ns == viewable_mtime + + +def test_unreadable_crs_does_not_fail_the_data_response(monkeypatch: pytest.MonkeyPatch) -> None: + def unreadable(*_args: object) -> None: + msg = "unreadable CRS" + raise RuntimeError(msg) + + monkeypatch.setattr(crs_functions, "geographic_info", unreadable) + assert crs_functions.active_crs(GeodePointSet3D()) is None From 4c72db6bd8c1ba8e8c4a79e46149103019bb39d7 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Fri, 23 Oct 2026 16:56:07 +0200 Subject: [PATCH 10/10] fix(crs): temporary workaround so packaged apps find the PROJ database PyInstaller loads libproj away from opengeode/share/proj, so PROJ cannot find proj.db: the CRS list is empty and conversions fail. TEMPORARY: default PROJ_DATA to the opengeode share folder and bundle it in the spec. To remove once OpenGeode sets PROJ_DATA itself and ships a PyInstaller hook. --- opengeodeweb-back.spec | 2 ++ src/opengeodeweb_back/crs_functions.py | 10 ++++++++-- tests/test_crs_routes.py | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/opengeodeweb-back.spec b/opengeodeweb-back.spec index 45f7236d..1fd3e0f4 100644 --- a/opengeodeweb-back.spec +++ b/opengeodeweb-back.spec @@ -5,6 +5,8 @@ from PyInstaller.utils.hooks import copy_metadata datas = [] datas += collect_data_files('opengeodeweb_back') +# TEMPORARY until OpenGeode ships a PyInstaller hook: PROJ database (proj.db) for the CRS +datas += collect_data_files('opengeode', subdir='share') datas += copy_metadata('opengeodeweb-back', recursive=True) diff --git a/src/opengeodeweb_back/crs_functions.py b/src/opengeodeweb_back/crs_functions.py index ef7ff216..f9ad706d 100644 --- a/src/opengeodeweb_back/crs_functions.py +++ b/src/opengeodeweb_back/crs_functions.py @@ -3,9 +3,12 @@ # Standard library imports import functools import logging +import os +from pathlib import Path from typing import TYPE_CHECKING # Third party imports +import opengeode as og import opengeode_geosciences as og_geosciences # Local application imports @@ -20,12 +23,15 @@ from opengeodeweb_back.geode_objects.geode_surface_mesh3d import GeodeSurfaceMesh3D if TYPE_CHECKING: - import opengeode as og - from opengeodeweb_back.geode_objects.geode_object import GeodeObject logger = logging.getLogger(__name__) +# TEMPORARY workaround, to remove once OpenGeode sets it itself (and ships a PyInstaller hook): +# PROJ looks for its database next to the loaded libproj, which packaged apps (PyInstaller) +# Load from elsewhere, so point it to the one opengeode ships +os.environ.setdefault("PROJ_DATA", str(Path(og.__file__).parent / "share" / "proj")) + CRS_GEODE_OBJECTS = ( GeodeBRep, GeodeSection, diff --git a/tests/test_crs_routes.py b/tests/test_crs_routes.py index ab8d97ef..98739682 100644 --- a/tests/test_crs_routes.py +++ b/tests/test_crs_routes.py @@ -1,6 +1,7 @@ from __future__ import annotations # Standard library imports +import os import shutil from pathlib import Path from typing import TYPE_CHECKING @@ -376,3 +377,8 @@ def unreadable(*_args: object) -> None: monkeypatch.setattr(crs_functions, "geographic_info", unreadable) assert crs_functions.active_crs(GeodePointSet3D()) is None + + +def test_proj_finds_its_database() -> None: + # Packaged apps move libproj away from the opengeode share folder + assert (Path(os.environ["PROJ_DATA"]) / "proj.db").is_file()