I want to draw curves between any two points in 3d space. The curve must be, umm, "vertical". I mean, x,y positions of the points of curve must be on the same line, but z values must change as if you sent a projectile from ground, it traveled in air, and hit the ground again. It does not need to be physically correct, an arc is OK.
This is the starting code:
import numpy as np
p1=np.array([1,1,1]) #x,y,z coordinates of the first point
p2=np.array([3,3,3]) #x,y,z coordinates of the second point
xi=np.linspace(p1[0],p2[0],100) #determine 100 x coordinates between two points
yi=np.linspace(p1[1],p2[1],100) #determine 100 y coordinates between two points
zi= ?? #determine 100 z coordinates between two points.
How can I determine those 100 z coordinates (zi)?
After determining zi it is trivial to draw lines between consecutive points(using mayavi or mplot3d) , giving the visual of a curve.
I ended up using scipy.interpolate to get the curve, and adding it to z coordinates of the line between points. As others said, there are more than one way to do this. This will be enough for my purpose.
### objective: draw an arc between points p1 and p2. z coordinates are raised.
import numpy as np
from scipy import interpolate
from mayavi import mlab
###inputs
p1=np.random.uniform(0,20,(3)) #first point
p2=np.random.uniform(0,20,(3)) #second point
npts = 100 # number of points to sample
y=np.array([0,.5,.75,.75,.5,0]) #describe your shape in 1d like this
amp=5 #curve height factor. bigger means heigher
#get the adder. This will be used to raise the z coords
x=np.arange(y.size)
xnew = np.linspace(x[0],x[-1] , npts) #sample the x coord
tck = interpolate.splrep(x,y,s=0)
adder = interpolate.splev(xnew,tck,der=0)*amp
adder[0]=adder[-1]=0
adder=adder.reshape((-1,1))
#get a line between points
shape3=np.vstack([np.linspace(p1[dim],p2[dim],npts) for dim in xrange(3)]).T
#raise the z coordinate
shape3[:,-1]=shape3[:,-1]+adder[:,-1]
#plot
x,y,z=(shape3[:,dim] for dim in xrange(3))
mlab.points3d(x,y,z,color=(0,0,0))
mlab.plot3d(x,y,z,tube_radius=1)
mlab.outline()
mlab.axes()
mlab.show()
There isn't one right answer to this question because the curvature of the arc isn't constrained. The basis for the math for this problem is projectile motion, which gives you two key equations:
x_2 - x_1 = v_1 cos theta dt
z_2 - z_1 = -1/2 g dt^2 + v_0 sin theta dt
where v_1 is the initial velocity of the projectile, theta is the angle from horizontal that the projectile is shot at, dt is the time it takes for the projectile to go from point 1 to point 2, and g is the gravitational constant. This neglects y for now for simplicity. The problem for you is that this gives you two equations, but you have three unknowns, v_1, theta, and dt.
You can add a constraint, for example, that the higher of p1 and p2 is the peak of the trajectory. If p2 is higher, for example,
v_2 = v_1 - g dt = 0
Solving those three equations gives you v_1, which gives the z coordinate over time:
z = -1/2 g t^2 + v_1 t + z_1
t = np.linspace(0, dt, 100) gives you a numpy vector of times, and you can plug that into your formula for z.
Related
I need to generate 2D random coordinates and find the distance from one central location.
import numpy as np
import matplotlib.pyplot as plt
coords = np.random.random_integers(0,50,10)
print(coords)
To sample coordinates you can also sample the x- and y-coordinates separately:
import numpy as np
import matplotlib.pyplot as plt
x = np.random.randint(0, 50, 10)
y = np.random.randint(0, 50, 10)
plt.scatter(x, y)
plt.show()
The output of the above should be a scatter plot with the 10 sampled points. Next you can determine the distance of all the points to a defined central point (x0, y0) as follows:
x0, y0 = 25, 25
d = np.sqrt((x0 - x)**2 + (y0 - y)**2)
print(d)
Where d contains the distances to the central point and d[i] is the distance of (x[i], y[i]) to (x0, y0).
There are some problems with your line of reasoning, moreover, your question is not very clear.
First, you need both coordinates for a point. At the moment you are creating only 10 random values. Are they x? y? Do you want only integer coordinates? I assumed that since you used a deprecated integer random value generator. In the answer you ask the distance from a central location, what do you mean exactly? Do you want the distance from each point to that location? An average?
I tried to answer your question considering the central location as the centroid of the random points.
I generated 10 points.
I calculated the centroid with coordinate xm and ym.
In this case, to calculate the centroid you need just to compute the mean of your x coordinates and y coordinates.
If you want a specific location you just need to put numbers on xm and ym.
After I created a list "d1" where I can store the distance, for each point, to the centroid.
The formula in the "for loop" is just the Euclidean distance.
import numpy as np
import matplotlib.pyplot as plt
num_points=10
coords_x=np.random.randint(0,50,num_points)
coords_y=np.random.randint(0,50,num_points)
xm=np.average(coords_x)
ym=np.average(coords_y)
d1=((coords_x-xm)**2+(coords_y-ym)**2)**0.5
print(d1)
I am trying to sample around 1000 points from a 3-D ellipsoid, uniformly. Is there some way to code it such that we can get points starting from the equation of the ellipsoid?
I want points on the surface of the ellipsoid.
Theory
Using this excellent answer to the MSE question How to generate points uniformly distributed on the surface of an ellipsoid? we can
generate a point uniformly on the sphere, apply the mapping f :
(x,y,z) -> (x'=ax,y'=by,z'=cz) and then correct the distortion
created by the map by discarding the point randomly with some
probability p(x,y,z).
Assuming that the 3 axes of the ellipsoid are named such that
0 < a < b < c
We discard a generated point with
p(x,y,z) = 1 - mu(x,y,y)/mu_max
probability, ie we keep it with mu(x,y,y)/mu_max probability where
mu(x,y,z) = ((acy)^2 + (abz)^2 + (bcx)^2)^0.5
and
mu_max = bc
Implementation
import numpy as np
np.random.seed(42)
# Function to generate a random point on a uniform sphere
# (relying on https://stackoverflow.com/a/33977530/8565438)
def randompoint(ndim=3):
vec = np.random.randn(ndim,1)
vec /= np.linalg.norm(vec, axis=0)
return vec
# Give the length of each axis (example values):
a, b, c = 1, 2, 4
# Function to scale up generated points using the function `f` mentioned above:
f = lambda x,y,z : np.multiply(np.array([a,b,c]),np.array([x,y,z]))
# Keep the point with probability `mu(x,y,z)/mu_max`, ie
def keep(x, y, z, a=a, b=b, c=c):
mu_xyz = ((a * c * y) ** 2 + (a * b * z) ** 2 + (b * c * x) ** 2) ** 0.5
return mu_xyz / (b * c) > np.random.uniform(low=0.0, high=1.0)
# Generate points until we have, let's say, 1000 points:
n = 1000
points = []
while len(points) < n:
[x], [y], [z] = randompoint()
if keep(x, y, z):
points.append(f(x, y, z))
Checks
Check if all points generated satisfy the ellipsoid condition (ie that x^2/a^2 + y^2/b^2 + z^2/c^2 = 1):
for p in points:
pscaled = np.multiply(p,np.array([1/a,1/b,1/c]))
assert np.allclose(np.sum(np.dot(pscaled,pscaled)),1)
Runs without raising any errors. Visualize the points:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
points = np.array(points)
ax.scatter(points[:, 0], points[:, 1], points[:, 2])
# set aspect ratio for the axes using https://stackoverflow.com/a/64453375/8565438
ax.set_box_aspect((np.ptp(points[:, 0]), np.ptp(points[:, 1]), np.ptp(points[:, 2])))
plt.show()
These points seem evenly distributed.
Problem with currently accepted answer
Generating a point on a sphere and then just reprojecting it without any further corrections to an ellipse will result in a distorted distribution. This is essentially the same as setting this posts's p(x,y,z) to 0. Imagine an ellipsoid where one axis is orders of magnitude bigger than another. This way, it is easy to see, that naive reprojection is not going to work.
Consider using Monte-Carlo simulation: generate a random 3D point; check if the point is inside the ellipsoid; if it is, keep it. Repeat until you get 1,000 points.
P.S. Since the OP changed their question, this answer is no longer valid.
J.F. Williamson, "Random selection of points distributed on curved surfaces", Physics in Medicine & Biology 32(10), 1987, describes a general method of choosing a uniformly random point on a parametric surface. It is an acceptance/rejection method that accepts or rejects each candidate point depending on its stretch factor (norm-of-gradient). To use this method for a parametric surface, several things have to be known about the surface, namely—
x(u, v), y(u, v) and z(u, v), which are functions that generate 3-dimensional coordinates from two dimensional coordinates u and v,
The ranges of u and v,
g(point), the norm of the gradient ("stretch factor") at each point on the surface, and
gmax, the maximum value of g for the entire surface.
The algorithm is then:
Generate a point on the surface, xyz.
If g(xyz) >= RNDU01()*gmax, where RNDU01() is a uniform random variate in [0, 1), accept the point. Otherwise, repeat this process.
Chen and Glotzer (2007) apply the method to the surface of a prolate spheroid (one form of ellipsoid) in "Simulation studies of a phenomenological model for elongated virus capsid formation", Physical Review E 75(5), 051504 (preprint).
Here is a generic function to pick a random point on a surface of a sphere, spheroid or any triaxial ellipsoid with a, b and c parameters. Note that generating angles directly will not provide uniform distribution and will cause excessive population of points along z direction. Instead, phi is obtained as an inverse of randomly generated cos(phi).
import numpy as np
def random_point_ellipsoid(a,b,c):
u = np.random.rand()
v = np.random.rand()
theta = u * 2.0 * np.pi
phi = np.arccos(2.0 * v - 1.0)
sinTheta = np.sin(theta);
cosTheta = np.cos(theta);
sinPhi = np.sin(phi);
cosPhi = np.cos(phi);
rx = a * sinPhi * cosTheta;
ry = b * sinPhi * sinTheta;
rz = c * cosPhi;
return rx, ry, rz
This function is adopted from this post: https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/
One way of doing this whch generalises for any shape or surface is to convert the surface to a voxel representation at arbitrarily high resolution (the higher the resolution the better but also the slower). Then you can easily select the voxels randomly however you want, and then you can select a point on the surface within the voxel using the parametric equation. The voxel selection should be completely unbiased, and the selection of the point within the voxel will suffer the same biases that come from using the parametric equation but if there are enough voxels then the size of these biases will be very small.
You need a high quality cube intersection code but with something like an elipsoid that can optimised quite easily. I'd suggest stepping through the bounding box subdivided into voxels. A quick distance check will eliminate most cubes and you can do a proper intersection check for the ones where an intersection is possible. For the point within the cube I'd be tempted to do something simple like a random XYZ distance from the centre and then cast a ray from the centre of the elipsoid and the selected point is where the ray intersects the surface. As I said above, it will be biased but with small voxels, the bias will probably be small enough.
There are libraries that do convex shape intersection very efficiently and cube/elipsoid will be one of the options. They will be highly optimised but I think the distance culling would probably be worth doing by hand whatever. And you will need a library that differentiates between a surface intersection and one object being totally inside the other.
And if you know your elipsoid is aligned to an axis then you can do the voxel/edge intersection very easily as a stack of 2D square intersection elipse problems with the set of squares to be tested defined as those that are adjacent to those in the layer above. That might be quicker.
One of the things that makes this approach more managable is that you do not need to write all the code for edge cases (it is a lot of work to get around issues with floating point inaccuracies that can lead to missing or doubled voxels at the intersection). That's because these will be very rare so they won't affect your sampling.
It might even be quicker to simply find all the voxels inside the elipse and then throw away all the voxels with 6 neighbours... Lots of options. It all depends how important performance is. This will be much slower than the opther suggestions but if you want ~1000 points then ~100,000 voxels feels about the minimum for the surface, so you probably need ~1,000,000 voxels in your bounding box. However even testing 1,000,000 intersections is pretty fast on modern computers.
Depending on what "uniformly" refers to, different methods are applicable. In any case, we can use the parametric equations using spherical coordinates (from Wikipedia):
where s = 1 refers to the ellipsoid given by the semi-axes a > b > c. From these equations we can derive the relevant volume/area element and generate points such that their probability of being generated is proportional to that volume/area element. This will provide constant volume/area density across the surface of the ellipsoid.
1. Constant volume density
This method generates points on the surface of an ellipsoid such that their volume density across the surface of the ellipsoid is constant. A consequence of this is that the one-dimensional projections (i.e. the x, y, z coordinates) are uniformly distributed; for details see the plot below.
The volume element for a triaxial ellipsoid is given by (see here):
and is thus proportional to sin(theta) (for 0 <= theta <= pi). We can use this as the basis for a probability distribution that indicates "how many" points should be generated for a given value of theta: where the area density is low/high, the probability for generating a corresponding value of theta should be low/high, too.
Hence, we can use the function f(theta) = sin(theta)/2 as our probability distribution on the interval [0, pi]. The corresponding cumulative distribution function is F(theta) = (1 - cos(theta))/2. Now we can use Inverse transform sampling to generate values of theta according to f(theta) from a uniform random distribution. The values of phi can be obtained directly from a uniform distribution on [0, 2*pi].
Example code:
import matplotlib.pyplot as plt
import numpy as np
from numpy import sin, cos, pi
rng = np.random.default_rng(seed=0)
a, b, c = 10, 3, 1
N = 5000
phi = rng.uniform(0, 2*pi, size=N)
theta = np.arccos(1 - 2*rng.random(size=N))
x = a*sin(theta)*cos(phi)
y = b*sin(theta)*sin(phi)
z = c*cos(theta)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.scatter(x, y, z, s=2)
plt.show()
which produces the following plot:
The following plot shows the one-dimensional projections (i.e. density plots of x, y, z):
import seaborn as sns
sns.kdeplot(data=dict(x=x, y=y, z=z))
plt.show()
2. Constant area density
This method generates points on the surface of an ellipsoid such that their area density is constant across the surface of the ellipsoid.
Again, we start by calculating the corresponding area element. For simplicity we can use SymPy:
from sympy import cos, sin, symbols, Matrix
a, b, c, t, p = symbols('a b c t p')
x = a*sin(t)*cos(p)
y = b*sin(t)*sin(p)
z = c*cos(t)
J = Matrix([
[x.diff(t), x.diff(p)],
[y.diff(t), y.diff(p)],
[z.diff(t), z.diff(p)],
])
print((J.T # J).det().simplify())
This yields
-a**2*b**2*sin(t)**4 + a**2*b**2*sin(t)**2 + a**2*c**2*sin(p)**2*sin(t)**4 - b**2*c**2*sin(p)**2*sin(t)**4 + b**2*c**2*sin(t)**4
and further simplifies to (dividing by (a*b)**2 and taking the sqrt):
sin(t)*np.sqrt(1 + ((c/b)**2*sin(p)**2 + (c/a)**2*cos(p)**2 - 1)*sin(t)**2)
Since for this case the area element is more complex, we can use rejection sampling:
import matplotlib.pyplot as plt
import numpy as np
from numpy import cos, sin
def f_redo(t, p):
return (
sin(t)*np.sqrt(1 + ((c/b)**2*sin(p)**2 + (c/a)**2*cos(p)**2 - 1)*sin(t)**2)
< rng.random(size=t.size)
)
rng = np.random.default_rng(seed=0)
N = 5000
a, b, c = 10, 3, 1
t = rng.uniform(0, np.pi, size=N)
p = rng.uniform(0, 2*np.pi, size=N)
redo = f_redo(t, p)
while redo.any():
t[redo] = rng.uniform(0, np.pi, size=redo.sum())
p[redo] = rng.uniform(0, 2*np.pi, size=redo.sum())
redo[redo] = f_redo(t[redo], p[redo])
x = a*np.sin(t)*np.cos(p)
y = b*np.sin(t)*np.sin(p)
z = c*np.cos(t)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.scatter(x, y, z, s=2)
plt.show()
which yields the following distribution:
The following plot shows the corresponding one-dimensional projections (x, y, z):
I have an n x n x n numpy array that contains density values on a cubic grid. I'm trying to align the principal axes of inertia of the density map with the cartesian x,y,z axes of the grid. I have the following so far:
import numpy as np
from scipy import ndimage
def center_rho(rho):
"""Move density map so its center of mass aligns with the center of the grid"""
rhocom = np.array(ndimage.measurements.center_of_mass(rho))
gridcenter = np.array(rho.shape)/2.
shift = gridcenter-rhocom
rho = ndimage.interpolation.shift(rho,shift,order=1,mode='wrap')
return rho
def inertia_tensor(rho,side):
"""Calculate the moment of inertia tensor for the given density map."""
halfside = side/2.
n = rho.shape[0]
x_ = np.linspace(-halfside,halfside,n)
x,y,z = np.meshgrid(x_,x_,x_,indexing='ij')
Ixx = np.sum(rho*(y**2 + z**2))
Iyy = np.sum(rho*(x**2 + z**2))
Izz = np.sum(rho*(x**2 + y**2))
Ixy = -np.sum(rho*x*y)
Iyz = -np.sum(rho*y*z)
Ixz = -np.sum(rho*x*z)
I = np.array([[Ixx, Ixy, Ixz],
[Ixy, Iyy, Iyz],
[Ixz, Iyz, Izz]])
return I
def principal_axes(I):
"""Calculate the principal inertia axes and order them in ascending order."""
w,v = np.linalg.eigh(I)
return w,v
#number of grid points along side
n = 10
#note n <= 3 produces unit eigenvectors, not sure why
#in practice, n typically between 10 and 50
np.random.seed(1)
rho = np.random.random(size=(n,n,n))
side = 1. #physical width of box, set to 1.0 for simplicity
rho = center_rho(rho)
I = inertia_tensor(rho,side)
PAw, PAv = principal_axes(I)
#print magnitude and direction of principal axes
print "Eigenvalues/eigenvectors before rotation:"
for i in range(3):
print PAw[i], PAv[:,i]
#sanity check that I = R * D * R.T
#where R is the rotation matrix and D is the diagonalized matrix of eigenvalues
D = np.eye(3)*PAw
print np.allclose(np.dot(PAv,np.dot(D,PAv.T)),I)
#rotate rho to align principal axes with cartesian axes
newrho = ndimage.interpolation.affine_transform(rho,PAv.T,order=1,mode='wrap')
#recalculate principal axes
newI = inertia_tensor(newrho,side)
newPAw, newPAv = principal_axes(newI)
#print magnitude and direction of new principal axes
print "Eigenvalues/eigenvectors before rotation:"
for i in range(3):
print newPAw[i], newPAv[:,i]
Here I'm assuming that the eigenvectors of the inertia tensor define the rotation matrix (which based on this question and Google results such as this webpage seems correct?) However this doesn't give me the correct result.
I expect the printed matrix to be:
[1 0 0]
[0 1 0]
[0 0 1]
(which could be wrong) but don't even get unit vectors to start with. What I get is:
Eigenvalues/eigenvectors before rotation:
102.405523732 [-0.05954221 -0.8616362 0.5040216 ]
103.177395578 [-0.30020273 0.49699978 0.81416801]
104.175688943 [-0.95201526 -0.10283129 -0.288258 ]
True
Eigenvalues/eigenvectors after rotation:
104.414931478 [ 0.38786 -0.90425086 0.17859172]
104.731536038 [-0.74968553 -0.19676735 0.63186566]
106.151322662 [-0.53622405 -0.37896304 -0.75422197]
I'm not sure if the problem is my code or my assumptions about rotating principal axes, but any help would be appreciated.
Here is the link to the code I developed to do such alignment.
Given a set of scatter points with coordinates (x,y,z), the objective is to match the eigenvector associated to the minimum eigenvalue with the X-axis of a 3D cartesian axis and the eigenvector associated to the median eigenvalue with the Y axis from the same 3D cartesian axis.
For this purpose, I followed the following steps:
Translate the set of points with centroid in (xmn, ymn, zmn) to a new set of points with centroid in (0,0,0) only by doing: (x-xmn, y-ymn, z-zmn).
Calculate the angle THETA (rotation around z) between the xy-projection of the eigenvector associated to the minimum eigenvalue (min_eigen) and the x-axis in a cartesian axis. After obtention of the resulting tetha, rotate the min_eigen the given theta so that it is contained in the xy-plane. Let's call this resulting vector: rotz
Calculate the angle PHI between rotz and x-axis in order to perform a rotation around the y-axis. Once the phi is obtained, a rotation is applied to rotz aound the y axis. With this last rotation, the eigenvector associated to the medium eigenvector (medium_eigen) is then in the yz proyection of the cartesian axis, so we will just need to find the angle between medium_eigen and the y-axis of the cartesian axis.
Calculate the angle ALPHA between the medium_eigen and y-axis. Apply the rotation around the x-axis aaaand: IT'S DONE!
NOTE: After applying steps 1,2,3 to your set of points, you have to recalculate the 3D_SVD (3D_single value decomposition) and from the resulting set of eigenvectors, then implement the 4th step with the new medium_eigen.
I really hope this helps.
The rotations are implemented by means of the rotation matrix defined here: Rotating a Vector in 3D Space
I have a set of data given here where in the first and second columns there are the sky coordinates (ra,dec), respectively and in the third and forth, the coordinates in a Cartesian system (x,y).
I need to make a two-dimensional interpolation surface using coordinates x and y and another using Ra and Dec. The problem is the existence of masked regions, as shown in the figure above. I can illustrate the missing data just by plotting them (There is non NaN value in the catalogue). That is what I so far tried and didn't give the right answer:
from scipy.interpolate import griddata
import numpy as np
import matplotlib.pyplot as plt
data = np.loadtxt('test.asc')
ra = data[:,0]
dec = data[:,1]
Xpos = data[:,2]
Ypos = data[:,3]
xi = np.linspace(Xpos.min(), Xpos.max(), 1000)
yi = np.linspace(Ypos.min(), Ypos.max(), 1000)
xi, yi = np.meshgrid(xi, yi, copy=False)
ra_int = griddata(data[:,2:4], ra, (xi.flatten(), yi.flatten()),
method='cubic')
dec_int = griddata(data[:,2:4], dec, (xi.flatten(), yi.flatten()),
method='cubic')
Using griddata fails and return just NaN values. Is there any way to do this interpolation in order to estimate the values of Ra and Dec from a given x and y coordinates even in the masked regions (map from x and y to ra and dec)?
If I get it right then it is like this:
just shift the Cartesian coordinate system to middle of the CCD and also the Equatoreal coordinates to middle of CCD. Then compute x,y separately. The only thing you need is to compute focus length f separately for x and y !!!
pos is the cartesian coordinate (x or y)
ang is the equatoreal coordinate (RA or Dec)
get edge point from the database
shift the angles to middle of CCD
compute focus (fx,fy) from it
f = pos/tan(ang)
now you can compute the projection for any entry in dataset
shift the angles to middle of CCD then compute x,y by
pos=f*tan(ang)
shift back from CCD middle to original Cartesian coordinates. You should check few points if is this approach correct
[notes]
x axis is mirrored in your output so just use x=-x at the end before shifting back to original Cartesian coordinates or leave focus f negative.
if your CCD is not axis aligned to equator then you need to compute the rotation (angle between X axis and equator) and apply rotation around Z axis after conversion before shifting back...
I would like to be able to plot two lines using direction and distance. It is a Drillhole trace, so I have the data in this format right now,
The depth is actually distance down the hole, not vertical depth. Azimuth is from magnetic north. Dip is based on 0 being horizontal. I want to plot two lines from the same point (0,0,0 is fine) and see how they differ, based on this kind of info.
I have no experience with Matplotlib but am comfortable with Python and would like to get to know this plotting tool. I have found this page and it helped to understand the framework, but I still can't figure out how to plot lines with 3d vectors. Can someone give me some pointers on how to do this or where to find the directions I need? Thank you
A script converting your coordinates to cartesian and plotting it with matplotlib with the comments included:
import numpy as np
import matplotlib.pyplot as plt
# import for 3d plot
from mpl_toolkits.mplot3d import Axes3D
# initializing 3d plot
fig = plt.figure()
ax = fig.add_subplot(111, projection = '3d')
# several data points
r = np.array([0, 14, 64, 114])
# get lengths of the separate segments
r[1:] = r[1:] - r[:-1]
phi = np.array([255.6, 255.6, 261.7, 267.4])
theta = np.array([-79.5, -79.5, -79.4, -78.8])
# convert to radians
phi = phi * 2 * np.pi / 360.
# in spherical coordinates theta is measured from zenith down; you are measuring it from horizontal plane up
theta = (90. - theta) * 2 * np.pi / 360.
# get x, y, z from known formulae
x = r*np.cos(phi)*np.sin(theta)
y = r*np.sin(phi)*np.sin(theta)
z = r*np.cos(theta)
# np.cumsum is employed to gradually sum resultant vectors
ax.plot(np.cumsum(x),np.cumsum(y),np.cumsum(z))
For a drillhole with 500 m you may use minimum curvature method, otherwise the position error will be really large. I implemented this in a python module for geostatistics (PyGSLIB). An example showing a complete desurvey process for a real drillhole database, including positions at assay/lithology intervals is shown at:
http://nbviewer.ipython.org/github/opengeostat/pygslib/blob/master/pygslib/Ipython_templates/demo_1.ipynb
This also shows how to export drillholes in VTK format to lad it in paraview.
Results shown in Paraview
The code in Cython to desurvey one interval is as follows:
cpdef dsmincurb( float len12,
float azm1,
float dip1,
float azm2,
float dip2):
"""
dsmincurb(len12, azm1, dip1, azm2, dip2)
Desurvey one interval with minimum curvature
Given a line with length ``len12`` and endpoints p1,p2 with
direction angles ``azm1, dip1, azm2, dip2``, this function returns
the differences in coordinate ``dz,dn,de`` of p2, assuming
p1 with coordinates (0,0,0)
Parameters
----------
len12, azm1, dip1, azm2, dip2: float
len12 is the length between a point 1 and a point 2.
azm1, dip1, azm2, dip2 are direction angles azimuth, with 0 or
360 pointing north and dip angles measured from horizontal
surface positive downward. All these angles are in degrees.
Returns
-------
out : tuple of floats, ``(dz,dn,de)``
Differences in elevation, north coordinate (or y) and
east coordinate (or x) in an Euclidean coordinate system.
See Also
--------
ang2cart,
Notes
-----
The equations were derived from the paper:
http://www.cgg.com/data//1/rec_docs/2269_MinimumCurvatureWellPaths.pdf
The minimum curvature is a weighted mean based on the
dog-leg (dl) value and a Ratio Factor (rf = 2*tan(dl/2)/dl )
if dl is zero we assign rf = 1, which is equivalent to balanced
tangential desurvey method. The dog-leg is zero if the direction
angles at the endpoints of the desurvey intervals are equal.
Example
--------
>>> dsmincurb(len12=10, azm1=45, dip1=75, azm2=90, dip2=20)
(7.207193374633789, 1.0084573030471802, 6.186459064483643)
"""
# output
cdef:
float dz
float dn
float de
# internal
cdef:
float i1
float a1
float i2
float a2
float DEG2RAD
float rf
float dl
DEG2RAD=3.141592654/180.0
i1 = (90 - dip1) * DEG2RAD
a1 = azm1 * DEG2RAD
i2 = (90 - dip2) * DEG2RAD
a2 = azm2 * DEG2RAD
# calculate the dog-leg (dl) and the Ratio Factor (rf)
dl = acos(cos(i2-i1)-sin(i1)*sin(i2)*(1-cos(a2-a1)))
if dl!=0.:
rf = 2*tan(dl/2)/dl # minimum curvature
else:
rf=1 # balanced tangential
dz = 0.5*len12*(cos(i1)+cos(i2))*rf
dn = 0.5*len12*(sin(i1)*cos(a1)+sin(i2)*cos(a2))*rf
de = 0.5*len12*(sin(i1)*sin(a1)+sin(i2)*sin(a2))*rf
return dz,dn,de