Skip to content

Commit 8edadb0

Browse files
bugfix for crs
1 parent d91c783 commit 8edadb0

11 files changed

Lines changed: 1063 additions & 257 deletions

File tree

energyml-utils/example/attic/crs_info_from_epc.py

Lines changed: 109 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import logging
2020
import sys
2121
from pathlib import Path
22-
from typing import Any, Optional
22+
from typing import Any, List, Optional
2323

2424

2525
# Run $env:PYTHONPATH="src" if it fails to be executed from the project root.
@@ -76,15 +76,23 @@ def _resolve_crs_from_grid(grid_obj: Any, epc: Epc) -> Optional[Any]:
7676
"""
7777
Walk from a representation object to its CRS document.
7878
79-
Tries the common path ``local_crs`` (or nested in ``grid2d_patch.geometry``
80-
for v2.0.1 ``Grid2DRepresentation``). Returns the resolved CRS object or
81-
``None``.
79+
The CRS DOR is always present in ``LocalCrs`` — the difference between
80+
RESQML versions is the depth of the path:
81+
82+
* **v2.2** : ``Geometry.LocalCrs`` (``PointGeometry`` sits directly on the object)
83+
* **v2.0.1**: ``Grid2dPatch.Geometry.LocalCrs`` (geometry is inside a patch sub-object)
84+
85+
``get_object_attribute_rgx`` resolves dot-delimited paths at exactly the
86+
depth specified, so we try the shallower v2.2 path first, then fall back
87+
to the deeper v2.0.1 path.
88+
89+
Returns the resolved CRS object or ``None``.
8290
"""
83-
# Direct attribute (v2.0.1 and many v2.2 envelopes)
84-
dor = get_object_attribute_rgx(grid_obj, "[Ll]ocal[_]?[Cc]rs")
91+
# v2.2: Geometry.LocalCrs (PointGeometry directly on the object)
92+
dor = get_object_attribute_rgx(grid_obj, "[Gg]eometry.[Ll]ocal[_]?[Cc]rs")
8593

8694
if dor is None:
87-
# Nested path used by Grid2DRepresentation v2.0.1
95+
# v2.0.1: Grid2dPatch.Geometry.LocalCrs (geometry wrapped in a patch)
8896
dor = get_object_attribute_rgx(
8997
grid_obj,
9098
"[Gg]rid2[Dd][Pp]atch.[Gg]eometry.[Ll]ocal[_]?[Cc]rs",
@@ -101,6 +109,31 @@ def _resolve_crs_from_grid(grid_obj: Any, epc: Epc) -> Optional[Any]:
101109
return candidates[0] if candidates else None
102110

103111

112+
def resolve_crs_from_triangulated_set(triangulated_obj: Any, epc: Epc) -> List[Optional[Any]]:
113+
"""
114+
Walk from a TriangulatedSetRepresentation to its CRS document.
115+
116+
Each patch of a TriangulatedSetRepresentation may reference a CRS via its
117+
``local_crs`` attribute. This function tries to resolve the first patch's CRS.
118+
"""
119+
dor = get_object_attribute_rgx(triangulated_obj, "triangle_patch.\d+.geometry.local_crs")
120+
# print(f" Found DOR for TriangulatedSetRepresentation patch CRS: {dor}")
121+
if dor is None:
122+
return []
123+
124+
if isinstance(dor, list):
125+
candidates = []
126+
for d in dor:
127+
uuid = get_obj_uuid(d)
128+
if uuid:
129+
obj_candidates = epc.get_object_by_uuid(uuid)
130+
print(f" Found DOR for TriangulatedSetRepresentation patch CRS: {d} → candidates: {len(obj_candidates)}")
131+
candidates.append(obj_candidates[0] if obj_candidates else None)
132+
return candidates
133+
134+
return None
135+
136+
104137
# ===========================================================================
105138
# RESQML v2.0.1 — testingPackageCpp.epc
106139
# ===========================================================================
@@ -124,7 +157,9 @@ def _resolve_crs_from_grid(grid_obj: Any, epc: Epc) -> Optional[Any]:
124157
check("z_offset", 15.0, info.z_offset, approx=True)
125158
check("projected_uom (raw from xsdata enum)", "M", info.projected_uom)
126159
check("vertical_uom (raw from xsdata enum)", "M", info.vertical_uom)
127-
check("z_increasing_downward", False, info.z_increasing_downward)
160+
# ZIncreasingDownward=true in the file; VerticalUnknownCrs sub-object carries
161+
# no direction field, so the sentinel correctly preserves the top-level value.
162+
check("z_increasing_downward", True, info.z_increasing_downward)
128163
check("areal_rotation_value", 0.0, info.areal_rotation_value, approx=True)
129164
check("projected_epsg_code", None, info.projected_epsg_code)
130165
check("vertical_epsg_code", None, info.vertical_epsg_code)
@@ -141,9 +176,9 @@ def _resolve_crs_from_grid(grid_obj: Any, epc: Epc) -> Optional[Any]:
141176
check("projected_epsg_code", 23031, info.projected_epsg_code)
142177
check("projected_uom", "M", info.projected_uom)
143178
check("vertical_uom", "M", info.vertical_uom)
144-
# This particular depth CRS has z_increasing_downward=False in the file
145-
# (the VerticalCrs it references has no direction set or direction=up)
146-
check("z_increasing_downward", False, info.z_increasing_downward)
179+
# ZIncreasingDownward=true in the raw file; the linked VerticalUnknownCrs
180+
# carries no direction field, so the sentinel correctly preserves the value.
181+
check("z_increasing_downward", True, info.z_increasing_downward)
147182
check("x_offset", 0.0, info.x_offset, approx=True)
148183
check("y_offset", 0.0, info.y_offset, approx=True)
149184
check("z_offset", 0.0, info.z_offset, approx=True)
@@ -215,14 +250,17 @@ def _resolve_crs_from_grid(grid_obj: Any, epc: Epc) -> Optional[Any]:
215250
info = extract_crs_info(resolved_crs, workspace=epc20)
216251
check(" projected_epsg_code", 23031, info.projected_epsg_code)
217252
check(" projected_uom", "M", info.projected_uom)
218-
check(" z_increasing_downward", False, info.z_increasing_downward)
253+
# Same LocalDepth3DCrs — ZIncreasingDownward=true in the raw file.
254+
check(" z_increasing_downward", True, info.z_increasing_downward)
219255

220256
# Grid 4e56b0e4 → also LocalDepth3DCrs (same uuid)
221257
grid_depth2 = epc20.get_object_by_uuid("4e56b0e4-2cd1-4efa-97dd-95f72bcf9f80")[0]
222258
resolved_crs = _resolve_crs_from_grid(grid_depth2, epc20)
223259
check("Grid 4e56b0e4 resolved CRS uuid", "0ae56ef3-fc79-405b-8deb-6942e0f2e77c",
224260
getattr(resolved_crs, "uuid", None))
225261

262+
263+
226264
# ===========================================================================
227265
# RESQML v2.2 / EML v2.3 — testingPackageCpp22.epc
228266
# ===========================================================================
@@ -321,21 +359,19 @@ def _resolve_crs_from_grid(grid_obj: Any, epc: Epc) -> Optional[Any]:
321359
check("z_increasing_downward", True, info.z_increasing_downward)
322360

323361
# ── Grid2D v2.2 — CRS note ────────────────────────────────────────────────
324-
section("v2.2 · Grid2DRepresentation — CRS resolution note")
362+
section("v2.2 · Grid2DRepresentation — CRS resolution")
325363
print("""
326-
In RESQML v2.2, Grid2DRepresentation does not embed a local_crs DOR
327-
in its geometry patch the same way v2.0.1 does. Instead the CRS is
328-
referenced via the containing LocalEngineeringCompoundCrs or through
329-
the representation's own schema-level CRS association.
330-
331-
The canonical way to obtain CRS info from a v2.2 representation is to:
332-
1. Retrieve all LocalEngineeringCompoundCrs objects from the EPC.
333-
2. Match the one that logically covers your representation (by
334-
consulting interpretation / framework associations).
335-
or use the helper ``get_crs_obj(repr_obj, workspace=epc)`` which walks
336-
the object hierarchy.
337-
338-
Direct example — iterate all compound CRS objects and extract info:
364+
In RESQML v2.2, Grid2DRepresentation DOES embed a LocalCrs DOR, but at
365+
a shallower path than v2.0.1:
366+
367+
v2.2 : Geometry.LocalCrs (PointGeometry sits directly on the object)
368+
v2.0.1: Grid2dPatch.Geometry.LocalCrs (geometry is wrapped in a patch sub-object)
369+
370+
Both paths are resolved by trying the shallower v2.2 path first with
371+
``get_object_attribute_rgx``, then falling back to the deeper v2.0.1 path.
372+
No indirect lookup through framework associations is needed.
373+
374+
All LocalEngineeringCompoundCrs objects in this EPC:
339375
""")
340376

341377
for obj in epc22.energyml_objects:
@@ -346,6 +382,51 @@ def _resolve_crs_from_grid(grid_obj: Any, epc: Epc) -> Optional[Any]:
346382
print(f" vertical_uom={info.vertical_uom} z_down={info.z_increasing_downward}")
347383
print(f" offsets: x={info.x_offset} y={info.y_offset} z={info.z_offset}")
348384

385+
# ── Grid2DRepresentation v2.2 → CRS via Geometry.LocalCrs ─────────────────
386+
387+
section("v2.2 · Grid2DRepresentation (uuid 4e56b0e4) → CRS via Geometry.LocalCrs")
388+
389+
grid22 = epc22.get_object_by_uuid("4e56b0e4-2cd1-4efa-97dd-95f72bcf9f80")
390+
if grid22:
391+
grid22 = grid22[0]
392+
resolved_crs22 = _resolve_crs_from_grid(grid22, epc22)
393+
check("resolved CRS type", "LocalEngineeringCompoundCrs",
394+
type(resolved_crs22).__name__ if resolved_crs22 else None)
395+
check("resolved CRS uuid", "6a18c177-93be-41ac-9084-f84bbb31f46d",
396+
getattr(resolved_crs22, "uuid", None))
397+
if resolved_crs22:
398+
info = extract_crs_info(resolved_crs22, workspace=epc22)
399+
check(" projected_epsg_code", 23031, info.projected_epsg_code)
400+
check(" projected_uom", "M", info.projected_uom)
401+
check(" vertical_uom", "M", info.vertical_uom)
402+
check(" z_increasing_downward", True, info.z_increasing_downward)
403+
check(" x_offset", 0.0, info.x_offset, approx=True)
404+
check(" y_offset", 0.0, info.y_offset, approx=True)
405+
check(" z_offset", 0.0, info.z_offset, approx=True)
406+
else:
407+
print(" [SKIP] Grid 4e56b0e4 not found in testingPackageCpp22.epc")
408+
409+
410+
# TriangulatedSetRepresentation 1a4112fa → LocalEngineeringCompoundCrs (6a18c177)
411+
triangulated_set = epc22.get_object_by_uuid("1a4112fa-c4ef-4c8d-aed0-47d9273bebc5")[0]
412+
resolved_crs_list = resolve_crs_from_triangulated_set(triangulated_set, epc22)
413+
check("TriangulatedSetRepresentation resolved CRS uuid", 5,
414+
len(resolved_crs_list))
415+
416+
for i, resolved_crs in enumerate(resolved_crs_list):
417+
check(f"{i}) patch {i} resolved CRS type", "LocalEngineeringCompoundCrs",
418+
type(resolved_crs).__name__ if resolved_crs else None)
419+
if resolved_crs:
420+
info = extract_crs_info(resolved_crs, workspace=epc22)
421+
check(" projected_epsg_code (resolved)", 23031, info.projected_epsg_code)
422+
check(" projected_uom", "M", info.projected_uom)
423+
check(" vertical_uom", "M", info.vertical_uom)
424+
check(" z_increasing_downward", True, info.z_increasing_downward)
425+
check(" x_offset", 0.0, info.x_offset, approx=True)
426+
check(" y_offset", 0.0, info.y_offset, approx=True)
427+
check(" z_offset", 0.0, info.z_offset, approx=True)
428+
check(" azimuth_reference", "grid north", info.azimuth_reference)
429+
349430
# ===========================================================================
350431
# Convenience helpers (delegates in helper.py)
351432
# ===========================================================================
@@ -361,7 +442,8 @@ def _resolve_crs_from_grid(grid_obj: Any, epc: Epc) -> Optional[Any]:
361442
)
362443

363444
depth_crs = epc20.get_object_by_uuid("0ae56ef3-fc79-405b-8deb-6942e0f2e77c")[0]
364-
check("is_z_reversed(LocalDepth3DCrs)", False, is_z_reversed(depth_crs))
445+
# ZIncreasingDownward=true in the raw file → is_z_reversed returns True.
446+
check("is_z_reversed(LocalDepth3DCrs)", True, is_z_reversed(depth_crs))
365447
check("get_projected_epsg_code", 23031, get_projected_epsg_code(depth_crs))
366448
check("get_projected_uom", "M", get_projected_uom(depth_crs))
367449

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""
2+
Dump the raw JSON for every CRS (and Grid2D) object referenced by
3+
``crs_info_from_epc.py``, so you can cross-check the expected values
4+
in the integration script against what is actually stored in the EPC files.
5+
6+
Run from the workspace root::
7+
8+
poetry run python example/attic/dump_crs_objects.py
9+
"""
10+
from __future__ import annotations
11+
12+
import json
13+
import logging
14+
import sys
15+
from pathlib import Path
16+
17+
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
18+
19+
from energyml.utils.epc import Epc
20+
from energyml.utils.serialization import serialize_json
21+
22+
logging.basicConfig(level=logging.ERROR)
23+
24+
_ROOT = Path(__file__).parent.parent.parent
25+
EPC20_PATH = str(_ROOT / "rc" / "epc" / "testingPackageCpp.epc")
26+
EPC22_PATH = str(_ROOT / "rc" / "epc" / "testingPackageCpp22.epc")
27+
28+
# ---------------------------------------------------------------------------
29+
# Objects to dump
30+
# (epc_key, uuid, label)
31+
# ---------------------------------------------------------------------------
32+
OBJECTS_EPC20 = [
33+
("dbd637d5-4528-4145-908b-5f7136824f6d", "LocalTime3DCrs"),
34+
("0ae56ef3-fc79-405b-8deb-6942e0f2e77c", "LocalDepth3DCrs"),
35+
("95330cec-164c-4165-9fb9-c56477ae7f8a", "LocalEngineeringCompoundCrs (v2.0.1 EPC)"),
36+
("811f8e68-c0e4-5f90-b9cf-03f7e3d53ca4", "LocalEngineering2DCrs (v2.0.1 EPC)"),
37+
("1f6cf904-336c-5202-a13d-7c9b142cd406", "VerticalCrs (v2.0.1 EPC)"),
38+
("030a82f6-10a7-4ecf-af03-54749e098624", "Grid2DRepresentation → LocalTime3DCrs"),
39+
("aa5b90f1-2eab-4fa6-8720-69dd4fd51a4d", "Grid2DRepresentation → LocalDepth3DCrs"),
40+
("4e56b0e4-2cd1-4efa-97dd-95f72bcf9f80", "Grid2DRepresentation (v2.0.1)"),
41+
]
42+
43+
OBJECTS_EPC22 = [
44+
("997796f5-da9d-5175-9fb7-e592957b73fb", "LocalEngineering2DCrs (no EPSG)"),
45+
("671ffdeb-f25c-513a-a4a2-1774d3ac20c6", "LocalEngineering2DCrs (EPSG 23031)"),
46+
("f0e9f421-b902-4392-87d8-6495c02f2fbe", "LocalEngineeringCompoundCrs (no EPSG)"),
47+
("6a18c177-93be-41ac-9084-f84bbb31f46d", "LocalEngineeringCompoundCrs (EPSG 23031)"),
48+
("65cd199f-156b-5112-ad3e-b4f54a2aa77b", "VerticalCrs-A — Direction=down → z_down=True"),
49+
("355174db-6226-57ae-a5a6-92f33825fed4", "VerticalCrs-B — Direction=down → z_down=True"),
50+
("4e56b0e4-2cd1-4efa-97dd-95f72bcf9f80", "Grid2DRepresentation (v2.2)"),
51+
("1a4112fa-c4ef-4c8d-aed0-47d9273bebc5", "TriangulatedSetRepresentation (v2.2)"),
52+
]
53+
54+
# ---------------------------------------------------------------------------
55+
56+
def _sep(title: str) -> None:
57+
print(f"\n{'═' * 70}")
58+
print(f" {title}")
59+
print(f"{'═' * 70}")
60+
61+
62+
def _dump(epc: Epc, uuid: str, label: str) -> None:
63+
print(f"\n── {label} [{uuid}]")
64+
candidates = epc.get_object_by_uuid(uuid)
65+
if not candidates:
66+
print(" *** NOT FOUND ***")
67+
return
68+
obj = candidates[0]
69+
print(f" type : {type(obj).__module__}.{type(obj).__name__}")
70+
try:
71+
raw = json.loads(serialize_json(obj))
72+
# Pretty-print, indented 4 spaces relative to the bullet
73+
text = json.dumps(raw, indent=2, ensure_ascii=False)
74+
for line in text.splitlines():
75+
print(f" {line}")
76+
except Exception as exc:
77+
print(f" *** serialization error: {exc} ***")
78+
79+
80+
def main() -> None:
81+
_sep(f"EPC 2.0.1 — {EPC20_PATH}")
82+
epc20 = Epc.read_file(EPC20_PATH)
83+
print(f" Loaded {len(epc20.energyml_objects)} objects.")
84+
for uuid, label in OBJECTS_EPC20:
85+
_dump(epc20, uuid, label)
86+
87+
_sep(f"EPC 2.2 — {EPC22_PATH}")
88+
epc22 = Epc.read_file(EPC22_PATH)
89+
print(f" Loaded {len(epc22.energyml_objects)} objects.")
90+
for uuid, label in OBJECTS_EPC22:
91+
_dump(epc22, uuid, label)
92+
93+
94+
if __name__ == "__main__":
95+
main()

energyml-utils/example/attic/main_test_3D.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ def export_all_representation(epc_path: str, output_dir: str, regex_type_filter:
4444
mesh_list=mesh_list,
4545
out=f,
4646
)
47-
export_stl_path = path.with_suffix(".stl")
48-
with export_stl_path.open("wb") as stl_f:
49-
export_stl(
50-
mesh_list=mesh_list,
51-
out=stl_f,
52-
)
47+
# export_stl_path = path.with_suffix(".stl")
48+
# with export_stl_path.open("wb") as stl_f:
49+
# export_stl(
50+
# mesh_list=mesh_list,
51+
# out=stl_f,
52+
# )
5353
export_vtk_path = path.with_suffix(".vtk")
5454
with export_vtk_path.open("wb") as vtk_f:
5555
export_vtk(
@@ -103,18 +103,18 @@ def export_all_representation_in_memory(epc_path: str, output_dir: str, regex_ty
103103
mesh_list=mesh_list,
104104
out=f,
105105
)
106-
export_stl_path = path.with_suffix(".stl")
107-
with export_stl_path.open("wb") as stl_f:
108-
export_stl(
109-
mesh_list=mesh_list,
110-
out=stl_f,
111-
)
112-
export_vtk_path = path.with_suffix(".vtk")
113-
with export_vtk_path.open("wb") as vtk_f:
114-
export_vtk(
115-
mesh_list=mesh_list,
116-
out=vtk_f,
117-
)
106+
# export_stl_path = path.with_suffix(".stl")
107+
# with export_stl_path.open("wb") as stl_f:
108+
# export_stl(
109+
# mesh_list=mesh_list,
110+
# out=stl_f,
111+
# )
112+
# export_vtk_path = path.with_suffix(".vtk")
113+
# with export_vtk_path.open("wb") as vtk_f:
114+
# export_vtk(
115+
# mesh_list=mesh_list,
116+
# out=vtk_f,
117+
# )
118118

119119
logging.info(f" ✓ Exported to {path.name}")
120120
except NotSupportedError:
@@ -135,11 +135,12 @@ def export_all_representation_in_memory(epc_path: str, output_dir: str, regex_ty
135135
import logging
136136

137137
logging.basicConfig(level=logging.DEBUG)
138-
epc_file = "rc/epc/testingPackageCpp.epc"
138+
epc_file = "rc/epc/testingPackageCpp22.epc"
139+
# epc_file = "rc/epc/testingPackageCpp.epc"
139140
# epc_file = "rc/epc/output-val.epc"
140141
# epc_file = "rc/epc/Volve_Horizons_and_Faults_Depth_originEQN.epc"
141142
output_directory = Path("exported_meshes") / Path(epc_file).name.replace(".epc", "_3D_export")
142143
# export_all_representation(epc_file, output_directory)
143144
# export_all_representation(epc_file, output_directory, regex_type_filter="Wellbore")
144-
export_all_representation(epc_file, str(output_directory), regex_type_filter="")
145-
# export_all_representation_in_memory(epc_file, str(output_directory), regex_type_filter="")
145+
# export_all_representation(epc_file, str(output_directory), regex_type_filter="")
146+
export_all_representation_in_memory(epc_file, str(output_directory), regex_type_filter="")
5.18 KB
Binary file not shown.

energyml-utils/src/energyml/utils/data/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
Contains functions to help the read of specific entities like Grid2DRepresentation, TriangulatedSetRepresentation etc.
77
It also contains functions to export data into OFF/OBJ format.
88
"""
9-
from .crs import CrsInfo, extract_crs_info # noqa: F401
9+
from .crs import CrsInfo, extract_crs_info, apply_from_crs_info, apply_axis_order_swap # noqa: F401

0 commit comments

Comments
 (0)