Hi,
I’m using wellpathpy in a workflow where some wells are not provided as deviation surveys (md, inc, azi), but instead as a sequence of Cartesian trajectory points such as (x, y, z) or (northing, easting, tvd/depth).
At the moment, wellpathpy appears to assume that the input survey is already available as measured depth, inclination, and azimuth. That works well for standard deviation-survey data, but it leaves a gap for cases where the source data is only available as sampled coordinates along the borehole path.
Use case
In practice, some well data sets are delivered as a position log:
- northing / easting / depth
- x / y / z
- regularly or irregularly sampled points along the borehole
For these wells, I still need to:
- reconstruct a usable deviation survey
- preserve curved-borehole behavior as well as possible
- resample intermediate points along the trajectory using the existing minimum-curvature workflow
Current limitation
There does not seem to be a built-in way to create a wellpathpy.deviation object directly from Cartesian trajectory points.
A straight segment-by-segment Cartesian-to-spherical conversion is often too crude for curved wells, especially if the resulting survey is later resampled. What is really needed is a helper that derives approximate (md, inc, azi) values from the Cartesian path so the existing minimum_curvature() and resample() functionality can be reused.
Proposed feature
It would be very useful to have a helper such as:
wp.deviation_from_xyz(x, y, z)
Or:
wp.deviation_from_position_log(northing, easting, depth)
Expected behavior
Given arrays of Cartesian well path coordinates, the function should:
- interpret them as successive stations along the borehole
- estimate inclination and azimuth at each station
- estimate measured depth along the curved trajectory
- return a normal deviation object compatible with the rest of the library
This would allow downstream code to do things like:
dev = wp.deviation_from_xyz(northing, easting, depth)
pos = dev.minimum_curvature().resample(depths=target_mds)
Why this matters
This would make wellpathpy usable in workflows where the original deviation survey is unavailable, but the trajectory itself is known as sampled coordinates. That seems fairly common in interpreted or exported well data.
At the moment, downstream users need to implement their own conversion logic outside the library, even though the result is conceptually still a deviation survey and is intended to feed directly into wellpathpy’s resampling/interpolation tools.
Notes
I already implemented a local workaround downstream that reconstructs an approximate deviation survey from [(northing, easting, depth)] points and then feeds that into a deviation object. It works well enough to bridge the gap, but this feels like functionality that would fit naturally inside wellpathpy itself..
If this sounds useful, I’d be happy to help refine the API proposal or share the current approach as a starting point.
My current implementation is shown below:
def deviationFromXYZ(self, northing, easting, depth):
"""Deviation survey
Compute an approximate deviation survey from the position log, i.e. the
measured that would be convertable to this well path. It is assumed
that inclination, azimuth, and measured-depth starts at 0.
Returns
-------
dev : deviation
The implementation is based on this [1] stackexchange answer by tma,
which is included verbatim for future reference.
In order to get a better picture you should look at the problem in
2d. Your arc from (x1,y1,z1) to (x2,y2,z2) lives in a 2d plane,
also in the same pane the tangents (a1,i1) and (a2, i2). The 2d
plane is given by the vector (x1,y1,y3) to (x2,y2,z2) and vector
converted from polar to Cartesian of (a1, i1). In case their
co-linear is just a straight line and your done. Given the angle
between the (x1,y1,z2) and (a1, i1) be alpha, then the angle
between (x2,y2,z2) and (a2, i2) is –alpha. Use the normal vector of
the 2d plane and rotate normalized vector (x1,y1,z1) to (x2,y2,z2)
by alpha (maybe –alpha) and converter back to polar coordinates,
which gives you (a2,i2). If d is the distance from (x1,y1,z1) to
(x2,y2,z2) then MD = d* alpha /sin(alpha).
In essence, the well path (in cartesian coordinates) is evaluated in
segments from top to bottom, and for every segment the inclination and
azimuth "downwards" are reconstructed. The reconstructed inc and azi is
used as "entry angle" of the well bore into the next segment. This uses
some assumptions deriving from knowing that the position log was
calculated with the min-curve method, since a straight
cartesian-to-spherical conversion could be very sensitive [2].
[1] https://math.stackexchange.com/a/1191620
[2] I observed low error on average, but some segments could be off by
80 degrees azimuth
"""
upper = zip(northing[:-1], easting[:-1], depth[:-1])
lower = zip(northing[1:], easting[1:], depth[1:])
# Assume the initial depth and angles are all zero, but this can likely be parametrised.
incs, azis, mds = [0], [0], [0]
i1, a1 = 0, 0
for up, lo in zip(upper, lower):
up = np.array(up)
lo = np.array(lo)
# Make two vectors
# v1 is the vector from the upper survey station to the lower
# v2 is the vector formed by the initial inc/azi (given by the
# previous iteration).
#
# The v1 and v2 vectors form a plane the well path arc lives in.
v1 = lo - up
v2 = np.array(wp.geometry.direction_vector(i1, a1))
alpha = wp.geometry.angle_between(v1, v2)
normal = wp.geometry.normal_vector(v1, v2)
# v3 is the "exit vector", i.e. the direction of the well bore
# at the lower survey station, which would in turn be "entry
# direction" in the next segment.
v3 = wp.geometry.rotate(v1, normal, -alpha)
i2, a2 = wp.geometry.spherical(*v3)
# d is the length of the vector (straight line) from the upper
# station to the lower station.
d = np.linalg.norm(v1)
incs.append(i2)
azis.append(a2)
if alpha == 0:
mds.append(d)
else:
mds.append(d * alpha / np.sin(alpha))
# The current lower station is the upper station in the next
# segment.
i1 = i2
a1 = a2
mds = np.cumsum(mds)
return wp.deviation(md=np.array(mds), inc=np.array(incs), azi=np.array(azis))
Hi,
I’m using
wellpathpyin a workflow where some wells are not provided as deviation surveys(md, inc, azi), but instead as a sequence of Cartesian trajectory points such as(x, y, z)or(northing, easting, tvd/depth).At the moment,
wellpathpyappears to assume that the input survey is already available as measured depth, inclination, and azimuth. That works well for standard deviation-survey data, but it leaves a gap for cases where the source data is only available as sampled coordinates along the borehole path.Use case
In practice, some well data sets are delivered as a position log:
For these wells, I still need to:
Current limitation
There does not seem to be a built-in way to create a
wellpathpy.deviationobject directly from Cartesian trajectory points.A straight segment-by-segment Cartesian-to-spherical conversion is often too crude for curved wells, especially if the resulting survey is later resampled. What is really needed is a helper that derives approximate
(md, inc, azi)values from the Cartesian path so the existingminimum_curvature()andresample()functionality can be reused.Proposed feature
It would be very useful to have a helper such as:
Expected behavior
Given arrays of Cartesian well path coordinates, the function should:
This would allow downstream code to do things like:
Why this matters
This would make wellpathpy usable in workflows where the original deviation survey is unavailable, but the trajectory itself is known as sampled coordinates. That seems fairly common in interpreted or exported well data.
At the moment, downstream users need to implement their own conversion logic outside the library, even though the result is conceptually still a deviation survey and is intended to feed directly into wellpathpy’s resampling/interpolation tools.
Notes
I already implemented a local workaround downstream that reconstructs an approximate deviation survey from [(northing, easting, depth)] points and then feeds that into a deviation object. It works well enough to bridge the gap, but this feels like functionality that would fit naturally inside wellpathpy itself..
If this sounds useful, I’d be happy to help refine the API proposal or share the current approach as a starting point.
My current implementation is shown below: