Skip to content

#13958 OSDU well path import: recover absolute UTM and target unit system#940

Open
magnesj wants to merge 12 commits into
devfrom
13958-osdu-wellpath-utm-coordinates
Open

#13958 OSDU well path import: recover absolute UTM and target unit system#940
magnesj wants to merge 12 commits into
devfrom
13958-osdu-wellpath-utm-coordinates

Conversation

@magnesj
Copy link
Copy Markdown
Owner

@magnesj magnesj commented May 5, 2026

Summary

  • Fetches the parent Well record (via Wellbore.WellID) and uses its SpatialLocation.AsIngestedCoordinates as the surface origin, since the Wellbore record itself does not carry a SpatialLocation. Trajectory parquet X/Y (offsets relative to that origin) are added back to recover absolute UTM coordinates — fixes wells landing at (0, 0).
  • Detects the trajectory's advertised length unit from AvailableTrajectoryStationProperties.MD.StationPropertyUnitID (typically ft or m) and converts MD/TVD/X/Y to meters before combining with the meter-frame surface origin and datum elevation.
  • Adds a Target unit system combobox (Meters / Feet) on the well import wizard's summary page; the resulting RigWellPath is scaled into the chosen unit at read time.
  • Adds scripts/tools/extract_osdu_wellpath.py, a standalone Python tool to fetch wellbore + well + trajectory records and inspect surface origin / parquet contents independently of ResInsight (uses ~/.resinsight/osdu_config.json and osdu_token.json automatically).

Issue: OPM#13958

Test plan

  • Import a known wellbore from OSDU and confirm the well surfaces at the correct absolute UTM (e.g. (457121.86, 7322122.99) for the test wellbore on Equinor's swe partition).
  • Toggle Target unit system between Meters and Feet on the wizard summary page and confirm imported well path coordinates and MD scale accordingly.
  • Verify a wellbore with no parent Well or no SpatialLocation still imports without crashing (will fall back to origin 0, 0 with a warning in the log).
  • Run python scripts/tools/extract_osdu_wellpath.py --wellbore-id ... --trajectory-id ... and confirm the printed absolute UTM matches what ResInsight imports.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

OSDU well path import: recover absolute UTM coordinates and support unit conversion

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Recover absolute UTM coordinates by fetching Well surface origin from OSDU parent record
• Detect and convert trajectory length units (feet/meters) to meters before coordinate
  transformation
• Add target unit system selector (Meters/Feet) on import wizard summary page
• Implement standalone Python tool to inspect wellbore trajectory data independently
Diagram
flowchart LR
  A["OSDU Wellbore Record"] -->|fetch parent Well| B["Well SpatialLocation"]
  B -->|extract surface origin| C["Surface Easting/Northing/CRS"]
  D["Trajectory Parquet"] -->|read MD/TVD/X/Y| E["Local Coordinates"]
  F["AvailableTrajectoryStationProperties"] -->|detect unit| G["Unit-to-Meters Factor"]
  E -->|apply factor| H["Coordinates in Meters"]
  C -->|add to X/Y| I["Absolute UTM Coordinates"]
  H -->|combine with origin| I
  J["Target Unit Selector"] -->|scale result| K["Final Well Path"]
  I -->|scale by target unit| K
Loading

Grey Divider

File Changes

1. ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.h ✨ Enhancement +15/-0

Add surface location and unit conversion fields to OSDU structures

ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.h


2. ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp ✨ Enhancement +125/-1

Implement blocking Well record fetch and surface location parsing

ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp


3. ApplicationLibCode/Commands/OsduCommands/RiuWellImportWizard.h ✨ Enhancement +6/-0

Add target unit combobox and surface location fields to WellInfo

ApplicationLibCode/Commands/OsduCommands/RiuWellImportWizard.h


View more (8)
4. ApplicationLibCode/Commands/OsduCommands/RiuWellImportWizard.cpp ✨ Enhancement +20/-1

Add target unit system selector and fetch surface location on summary page

ApplicationLibCode/Commands/OsduCommands/RiuWellImportWizard.cpp


5. ApplicationLibCode/Commands/OsduCommands/RicWellPathsImportOsduFeature.cpp ✨ Enhancement +5/-0

Populate surface location and unit conversion fields in RimOsduWellPath

ApplicationLibCode/Commands/OsduCommands/RicWellPathsImportOsduFeature.cpp


6. ApplicationLibCode/ProjectDataModel/WellPath/RimOsduWellPath.h ✨ Enhancement +20/-0

Add surface location and unit conversion property fields

ApplicationLibCode/ProjectDataModel/WellPath/RimOsduWellPath.h


7. ApplicationLibCode/ProjectDataModel/WellPath/RimOsduWellPath.cpp ✨ Enhancement +100/-0

Implement getters/setters for surface location and unit conversion

ApplicationLibCode/ProjectDataModel/WellPath/RimOsduWellPath.cpp


8. ApplicationLibCode/ProjectDataModel/WellPath/RimOsduWellPathDataLoader.cpp ✨ Enhancement +6/-1

Pass surface location and unit parameters to parquet reader

ApplicationLibCode/ProjectDataModel/WellPath/RimOsduWellPathDataLoader.cpp


9. ApplicationLibCode/FileInterface/RifOsduWellPathReader.h ✨ Enhancement +6/-1

Add surface location and unit conversion parameters to reader function

ApplicationLibCode/FileInterface/RifOsduWellPathReader.h


10. ApplicationLibCode/FileInterface/RifOsduWellPathReader.cpp 🐞 Bug fix +16/-5

Apply surface origin and unit conversion to trajectory coordinates

ApplicationLibCode/FileInterface/RifOsduWellPathReader.cpp


11. scripts/tools/extract_osdu_wellpath.py ✨ Enhancement +206/-0

Standalone Python tool to inspect OSDU wellbore trajectory data

scripts/tools/extract_osdu_wellpath.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 5, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. pd unused import 📘 Rule violation ⚙ Maintainability
Description
The new script imports pandas as pd but never uses it, which will fail Ruff linting. This violates
the requirement that modified/added Python code passes ruff check --fix with no remaining
findings.
Code

scripts/tools/extract_osdu_wellpath.py[R175-183]

+        import pandas as pd
+        import pyarrow.parquet as pq
+    except ImportError:
+        print("pandas / pyarrow not installed; skipping parquet inspection.", file=sys.stderr)
+        return 0
+
+    table = pq.read_table(io.BytesIO(parquet_bytes))
+    df = table.to_pandas()
+    print(f"Columns: {list(df.columns)}")
Evidence
PR Compliance ID 4 requires all added Python code to pass Ruff formatting and linting. In
extract_osdu_wellpath.py, import pandas as pd is present but no pd usage exists in the
subsequent code, which is a standard Ruff unused-import violation.

CLAUDE.md
scripts/tools/extract_osdu_wellpath.py[175-183]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`scripts/tools/extract_osdu_wellpath.py` imports `pandas as pd` but never uses `pd`, which will trigger Ruff's unused-import lint error.
## Issue Context
The PR compliance checklist requires new/modified Python code to be Ruff-linted/formatted (`ruff format` and `ruff check --fix`) with no remaining findings.
## Fix Focus Areas
- scripts/tools/extract_osdu_wellpath.py[175-183]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Wizard surface fetch deadlock 🐞 Bug ☼ Reliability
Description
WellSummaryPage::updateSummaryDisplay holds m_mutex while calling
requestWellSurfaceLocationBlocking(), which runs a nested QEventLoop that processes user input; a
re-entrant onFilterChanged()/updateSummaryDisplay will attempt to re-lock m_mutex and freeze the
wizard. The surface-location request also has no timeout, so the nested loop can block indefinitely
on slow/hung networking.
Code

ApplicationLibCode/Commands/OsduCommands/RiuWellImportWizard.cpp[R725-740]

           if ( shouldIncludeTrajectory( w.existenceKind ) )
           {
               QString wellboreTrajectoryId = w.id;
+                    auto         location           = m_osduConnector->requestWellSurfaceLocationBlocking( wellbore.value().wellId );
+                    const double targetUnitToMeters = m_targetUnitComboBox->currentData().toDouble();
               wiz->addWellInfo( { .name                 = wellbore.value().name,
                                   .wellId               = wellbore.value().wellId,
                                   .wellboreId           = w.wellboreId,
                                   .wellboreTrajectoryId = wellboreTrajectoryId,
                                   .existenceKind        = w.existenceKind,
-                                        .datumElevation       = wellbore.value().datumElevation } );
+                                        .datumElevation       = wellbore.value().datumElevation,
+                                        .surfaceEasting       = location.easting,
+                                        .surfaceNorthing      = location.northing,
+                                        .crs                  = location.crs,
+                                        .unitToMeters         = w.unitToMeters,
+                                        .targetUnitToMeters   = targetUnitToMeters } );
Evidence
updateSummaryDisplay() takes a QMutexLocker for the whole rebuild and then does a blocking
surface-location fetch inside that locked region. requestWellSurfaceLocationBlocking() uses
loop.exec() (no timeout / no ExcludeUserInputEvents), which allows re-entrant UI signals (e.g.,
combobox changes) to fire while the mutex is held; the repo’s own requestTokenBlocking() shows the
safer pattern (timeout + ExcludeUserInputEvents).

ApplicationLibCode/Commands/OsduCommands/RiuWellImportWizard.cpp[704-741]
ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[803-831]
ApplicationLibCode/Application/Tools/Cloud/RiaCloudConnector.cpp[346-360]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WellSummaryPage::updateSummaryDisplay()` holds `m_mutex` while calling `requestWellSurfaceLocationBlocking()`, which runs a nested `QEventLoop` that processes user input events and has no timeout. This can deadlock on re-entrancy (e.g., `currentIndexChanged` firing while the loop is running) and can hang indefinitely.
### Issue Context
`RiaCloudConnector::requestTokenBlocking()` already demonstrates a safer approach with a `QTimer` timeout and `ExcludeUserInputEvents`.
### Fix Focus Areas
- ApplicationLibCode/Commands/OsduCommands/RiuWellImportWizard.cpp[704-741]
- ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[803-831]
- ApplicationLibCode/Application/Tools/Cloud/RiaCloudConnector.cpp[346-360]
### Suggested fix
- Do not call blocking network I/O while holding `WellSummaryPage::m_mutex`:
- Copy the needed `m_wellboreTrajectories` data to a local structure under lock, then release the lock before any network calls.
- Add a timeout + exclude user input for the nested loop in `requestWellSurfaceLocationBlocking()` (mirror `requestTokenBlocking()`):
- Use a `QTimer` to quit the loop after a sensible timeout.
- Call `loop.exec(QEventLoop::ExcludeUserInputEvents)` to prevent UI-triggered re-entrancy.
- Consider making surface-location retrieval async (preferred), but the above changes are the minimal safe fix.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Surface request not cancellable 🐞 Bug ☼ Reliability
Description
requestWellSurfaceLocationBlocking() creates a QNetworkReply that is not tracked in m_replies, so
cancelRequestForId() cannot abort it. This makes cancellation/cleanup behavior inconsistent with
other OSDU downloads and can leave long-running requests active.
Code

ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[R826-830]

+    QNetworkReply* reply = m_networkAccessManager->get( networkRequest );
+
+    QEventLoop loop;
+    connect( reply, &QNetworkReply::finished, &loop, &QEventLoop::quit );
+    loop.exec();
Evidence
Other downloads store replies in m_replies under m_repliesMutex and cancelRequestForId()
aborts based on that map. The new surface-location GET bypasses that mechanism entirely.

ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[745-753]
ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[787-798]
ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[822-831]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`requestWellSurfaceLocationBlocking()` issues a `QNetworkAccessManager::get()` but does not store the resulting reply in `m_replies`, so `cancelRequestForId()` cannot abort it.
### Issue Context
The connector already tracks parquet download replies in `m_replies` and aborts them via `cancelRequestForId()`.
### Fix Focus Areas
- ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[745-753]
- ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[787-798]
- ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[822-831]
### Suggested fix
- Either:
- Add the surface-location reply to `m_replies` under a deterministic id (e.g., `recordId` or `"wellSurface:" + recordId`) and remove it on completion, or
- Add a dedicated tracking map for surface-location requests and extend cancellation to cover it.
- Prefer reusing `makeDownloadRequest()` to keep header behavior consistent.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Silent unit parse fallback🐞 Bug ≡ Correctness
Description
parseWellTrajectory() silently assumes meters when MD StationPropertyUnitID is missing or doesn’t
match the expected trailing-colon format, because unitSymbol remains empty and no warning is logged.
This can produce incorrectly scaled MD/TVD/X/Y values (e.g., feet interpreted as meters).
Code

ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[R456-484]

+                double     unitToMeters = 1.0;
+                QString    mdUnitId;
+                QJsonArray availableProps = dataObj["AvailableTrajectoryStationProperties"].toArray();
+                for ( const QJsonValue& propValue : availableProps )
+                {
+                    QJsonObject propObj = propValue.toObject();
+                    if ( propObj["Name"].toString() == "MD" )
+                    {
+                        mdUnitId = propObj["StationPropertyUnitID"].toString();
+                        break;
+                    }
+                }
+                // OSDU unit-of-measure ids look like "data:reference-data--UnitOfMeasure:ft:" — the symbol is
+                // between the last two colons.
+                QString unitSymbol;
+                if ( mdUnitId.endsWith( ':' ) )
+                {
+                    int lastColon       = mdUnitId.lastIndexOf( ':', mdUnitId.length() - 2 );
+                    if ( lastColon >= 0 ) unitSymbol = mdUnitId.mid( lastColon + 1, mdUnitId.length() - lastColon - 2 );
+                }
+                if ( unitSymbol.compare( "ft", Qt::CaseInsensitive ) == 0 )
+                {
+                    unitToMeters = 0.3048;
+                }
+                else if ( unitSymbol.compare( "m", Qt::CaseInsensitive ) != 0 && !unitSymbol.isEmpty() )
+                {
+                    RiaLogging::warning(
+                        QString( "Unrecognized MD unit '%1' for trajectory %2; assuming meters." ).arg( unitSymbol ).arg( id ) );
+                }
Evidence
The unit extraction only runs when mdUnitId.endsWith(':'); otherwise unitSymbol stays empty and
the code neither warns nor changes unitToMeters from 1.0. The warning path is only taken for
non-empty, non-"m" unit symbols.

ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[452-488]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Unit parsing for trajectories silently falls back to meters when unit metadata is missing or formatted unexpectedly.
### Issue Context
`unitToMeters` is used downstream to scale MD/TVD/X/Y before combining with the (meter-frame) surface origin and datum elevation.
### Fix Focus Areas
- ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[452-488]
### Suggested fix
- If `mdUnitId` is empty, log a warning that unit could not be determined (include trajectory id).
- If `mdUnitId` is non-empty but can’t be parsed, log a warning and include the raw `mdUnitId`.
- Make parsing more robust:
- Split on `':'` and take the last non-empty token as the symbol (handles both with/without trailing colon).
- Optionally support common variants (e.g., `feet`, `ftUS`) if they occur in your OSDU partitions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

5. Tool doesn't normalize IDs 🐞 Bug ☼ Reliability
Description
extract_osdu_wellpath.py passes user-provided record IDs verbatim to the storage API, but OSDU IDs
commonly include trailing ':' which the API may reject. This can cause the inspection tool to fail
to fetch records unless users manually strip the colon.
Code

scripts/tools/extract_osdu_wellpath.py[R65-69]

+def fetch_storage_record(server: str, partition: str, token: str, record_id: str) -> dict:
+    url = f"{server.rstrip('/')}/api/storage/v2/records/{record_id}"
+    resp = requests.get(url, headers=_headers(partition, token, "application/json"), timeout=30)
+    resp.raise_for_status()
+    return resp.json()
Evidence
The tool builds the storage URL with the raw record_id and only strips ':' for the parent
WellID, not for --wellbore-id / --trajectory-id inputs.

scripts/tools/extract_osdu_wellpath.py[65-69]
scripts/tools/extract_osdu_wellpath.py[138-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The standalone inspector may 404 on common OSDU ids ending with ':' because it does not normalize user-provided ids.
### Issue Context
The C++ importer explicitly strips trailing ':' before calling the storage API.
### Fix Focus Areas
- scripts/tools/extract_osdu_wellpath.py[65-69]
- scripts/tools/extract_osdu_wellpath.py[125-160]
### Suggested fix
- Normalize ids in `fetch_storage_record()` (e.g., `record_id = record_id.rstrip(':')`).
- Optionally apply the same normalization to `trajectory_id` in `fetch_trajectory_parquet()` if needed by the endpoint.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +175 to +183
import pandas as pd
import pyarrow.parquet as pq
except ImportError:
print("pandas / pyarrow not installed; skipping parquet inspection.", file=sys.stderr)
return 0

table = pq.read_table(io.BytesIO(parquet_bytes))
df = table.to_pandas()
print(f"Columns: {list(df.columns)}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. pd unused import 📘 Rule violation ⚙ Maintainability

The new script imports pandas as pd but never uses it, which will fail Ruff linting. This violates
the requirement that modified/added Python code passes ruff check --fix with no remaining
findings.
Agent Prompt
## Issue description
`scripts/tools/extract_osdu_wellpath.py` imports `pandas as pd` but never uses `pd`, which will trigger Ruff's unused-import lint error.

## Issue Context
The PR compliance checklist requires new/modified Python code to be Ruff-linted/formatted (`ruff format` and `ruff check --fix`) with no remaining findings.

## Fix Focus Areas
- scripts/tools/extract_osdu_wellpath.py[175-183]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 725 to +740
if ( shouldIncludeTrajectory( w.existenceKind ) )
{
QString wellboreTrajectoryId = w.id;
auto location = m_osduConnector->requestWellSurfaceLocationBlocking( wellbore.value().wellId );
const double targetUnitToMeters = m_targetUnitComboBox->currentData().toDouble();
wiz->addWellInfo( { .name = wellbore.value().name,
.wellId = wellbore.value().wellId,
.wellboreId = w.wellboreId,
.wellboreTrajectoryId = wellboreTrajectoryId,
.existenceKind = w.existenceKind,
.datumElevation = wellbore.value().datumElevation } );
.datumElevation = wellbore.value().datumElevation,
.surfaceEasting = location.easting,
.surfaceNorthing = location.northing,
.crs = location.crs,
.unitToMeters = w.unitToMeters,
.targetUnitToMeters = targetUnitToMeters } );
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Wizard surface fetch deadlock 🐞 Bug ☼ Reliability

WellSummaryPage::updateSummaryDisplay holds m_mutex while calling
requestWellSurfaceLocationBlocking(), which runs a nested QEventLoop that processes user input; a
re-entrant onFilterChanged()/updateSummaryDisplay will attempt to re-lock m_mutex and freeze the
wizard. The surface-location request also has no timeout, so the nested loop can block indefinitely
on slow/hung networking.
Agent Prompt
### Issue description
`WellSummaryPage::updateSummaryDisplay()` holds `m_mutex` while calling `requestWellSurfaceLocationBlocking()`, which runs a nested `QEventLoop` that processes user input events and has no timeout. This can deadlock on re-entrancy (e.g., `currentIndexChanged` firing while the loop is running) and can hang indefinitely.

### Issue Context
`RiaCloudConnector::requestTokenBlocking()` already demonstrates a safer approach with a `QTimer` timeout and `ExcludeUserInputEvents`.

### Fix Focus Areas
- ApplicationLibCode/Commands/OsduCommands/RiuWellImportWizard.cpp[704-741]
- ApplicationLibCode/Application/Tools/Cloud/RiaOsduConnector.cpp[803-831]
- ApplicationLibCode/Application/Tools/Cloud/RiaCloudConnector.cpp[346-360]

### Suggested fix
- Do not call blocking network I/O while holding `WellSummaryPage::m_mutex`:
  - Copy the needed `m_wellboreTrajectories` data to a local structure under lock, then release the lock before any network calls.
- Add a timeout + exclude user input for the nested loop in `requestWellSurfaceLocationBlocking()` (mirror `requestTokenBlocking()`):
  - Use a `QTimer` to quit the loop after a sensible timeout.
  - Call `loop.exec(QEventLoop::ExcludeUserInputEvents)` to prevent UI-triggered re-entrancy.
- Consider making surface-location retrieval async (preferred), but the above changes are the minimal safe fix.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@magnesj magnesj force-pushed the 13958-osdu-wellpath-utm-coordinates branch from 063460d to 79dbab3 Compare May 5, 2026 10:47
magnesj added 12 commits May 6, 2026 09:21
…lLocation

The wellbore trajectory parquet stores X/Y as offsets relative to the
wellbore surface point. To recover absolute UTM coordinates, parse
SpatialLocation.AsIngestedCoordinates on both the wellbore and the
trajectory record and surface easting/northing/CRS through OsduWellbore
and OsduWellboreTrajectory.
Forward the surface easting/northing/CRS extracted from OSDU through
RimOsduWellPath, the well import wizard, and the data loader so the
parquet reader can shift the trajectory's local X/Y back into absolute
UTM coordinates.
Fetches a wellbore record and a wellbore-trajectory record from OSDU,
dumps their SpatialLocation, downloads the trajectory parquet and prints
the absolute UTM coordinates after applying the wellbore surface origin.

Reads server / partition from ~/.resinsight/osdu_config.json and the
bearer token from ~/.resinsight/osdu_token.json by default. Useful for
validating the import path independently of ResInsight.
Wellbore records do not carry a SpatialLocation block; the surface point
lives on the parent Well referenced by Wellbore.WellID. Add a blocking
lookup that GETs the Well record from the storage API, parses
data.SpatialLocation.AsIngestedCoordinates and caches the result, then
populate WellInfo.surfaceEasting/Northing/crs from it instead of from
the wellbore parsing path.
When the Wellbore has no SpatialLocation, fall back to fetching the
parent Well record so the surface origin is reported alongside the
trajectory parquet inspection. Add --full flag to dump the entire data
block of each record.
The OSDU well trajectory parquet files specify MD, TVD, X, and Y values in an advertised length unit (e.g., feet or meters). This change detects the unit and converts all trajectory station properties to meters to ensure correct spatial alignment with the well's surface origin and datum elevation, which are consistently handled in meters.
The well summary page now offers a Meters / Feet combobox so the user
can pick the unit system the imported well path is stored in. The
selection is propagated through WellInfo, RimOsduWellPath, and the
parquet reader, which scales the meter-frame coordinates by 1 / target
factor before producing the final RigWellPath geometry.
Add shared helpers that parse OSDU UnitOfMeasure ids and the linear unit
embedded in a CRS WKT, and apply them at every JSON-parse entry point so
the surface origin (from the Well's SpatialLocation) and the wellbore's
datum elevation are stored in meters regardless of how OSDU advertises
them. Refactor the existing trajectory MD-unit detection to use the same
helper. The well-path reader's invariant that surface origin and datum
are already meters is now enforced at parse time.
…stem

WellSummaryPage now picks Feet as the combobox default when the first
loaded RimEclipseCase reports UNITS_FIELD; otherwise it stays on Meters.
This matches the imported well path to the case's coordinate frame so
users with field-unit cases do not have to flip the selector every time.
The previous wording claimed MD/TVD/X/Y all advertise the same length
unit, which is not true for real OSDU records (X/Y are sometimes tagged
as "dega" while the values are clearly metric). Document that we treat
MD's unit as canonical on purpose and apply it uniformly.
…ation

The trajectory's TVD column is referenced to its vertical CRS (typically
EPSG:5715 / MSL Depth, positive=down), so Z = -TVD lands the imported
well path in the same frame as the simulation well path and the case
grid. The previous +datumElevation term shifted every imported point up
by the RKB elevation, producing a constant offset (~20 m for typical
North Sea wells) between the imported and simulation trajectories. The
datum is still preserved on RigWellPath::setDatumElevation as metadata
for depth-relative plots, scaled into the target unit system.
@magnesj magnesj force-pushed the 13958-osdu-wellpath-utm-coordinates branch from 79dbab3 to 38e57ea Compare May 6, 2026 07:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant