Plotting a line from a coordinate with and angle - python

I basically want to plot a line from a coordinate (x, y) with a given angle (calculating the tangent value).
With a simple line of code like this pl.plot([x1, x2], [y1, y2], 'k-', lw=1) I can plot a line between two points but for this I need to calculate (x2, y2) coordinate. My (x1, y1) coordinate is fixed and the angle is known. Calculating (x2, y2) causes a problem at some point so I just want to plot the line from (x1, y1) with an angle (and preferably with a length).
The simplest solution I came up with that was to use point-slope function which is y - y1 = m(x - X1). Interpreting thiss and searching a little I used this piece of code:
x1 = 10
y1 = -50
angle = 30
sl = tan(radians(angle))
x = np.array(range(-10,10))
y = sl*(x-x1) + y1
pl.plot(x,y)
pl.show
sl is here slope and x1 and y1 are the coordinates. I needed to explain myself since this found to be a poor question.
So now, any ideas on how I can do/solve that?

I'm not really sure what exactly you want from the explanation, but I think this will do something close to what you asked for.
You should use trigonometry to get the new point if you know the angle and length of a line you want to use.
import numpy as np
import math
import matplotlib.pyplot as plt
def plot_point(point, angle, length):
'''
point - Tuple (x, y)
angle - Angle you want your end point at in degrees.
length - Length of the line you want to plot.
Will plot the line on a 10 x 10 plot.
'''
# unpack the first point
x, y = point
# find the end point
endy = y + length * math.sin(math.radians(angle))
endx = length * math.cos(math.radians(angle))
# plot the points
fig = plt.figure()
ax = plt.subplot(111)
ax.set_ylim([0, 10]) # set the bounds to be 10, 10
ax.set_xlim([0, 10])
ax.plot([x, endx], [y, endy])
fig.show()

Inspired by this website, given a WGS84 coordinate, a bearing (sometimes referred to as forward azimuth) and a distance, you can compute a resulting destination point with the following logic:
import math
distance = 100 # kilometres
radius = 6371 # earth's radius in kilometres
lon, lat = -7.83197, 37.040893
bearing = 40
δ = distance / radius
θ = math.radians(bearing)
φ1 = math.radians(lat)
λ1 = math.radians(lon)
sinφ2 = math.sin(φ1) * math.cos(δ) + math.cos(φ1) * math.sin(δ) * math.cos(θ)
φ2 = math.asin(sinφ2)
y = math.sin(θ) * math.sin(δ) * math.cos(φ1)
x = math.cos(δ) - math.sin(φ1) * sinφ2
λ2 = λ1 + math.atan2(y, x)
lat2 = math.degrees(φ2)
lon2 = math.degrees(λ2)
Which will yield
>>> lon2, lat2
(-7.831861171142511, 37.04091627610624)

Standard module for complex numbers, cmath, makes it easy.
import cmath
pt = cmath.rect(r, angle)
x = pt.real
y = pt.imag
Given the length of the line (or radius), r, and the angle in radians, we can get the terminal point x and y coordinates (x, y) for the line starting from origin (0, 0).
Not starting from origin: if line starts from any other point (x1, y1), simply add to get (x2, y2) as x2 = x1 + x and y2 = y1 + y
Degrees to Radians: if angle is available in degrees, use math.radians(deg) to get the same in radians. Of course, remember to import math before use.
cmath.rect(r, phi) is the function you will call. It returns a complex number! Simply take its real and imaginary parts as the x and y values you need.

What you want is kind new and is called axline.
import numpy as np
import matplotlib.pyplot as plt
x1 = 10
y1 = -50
angle = 30
sl = np.tan(np.radians(angle))
x = np.arange(-10,10)
y = sl*(x-x1) + y1
plt.plot(x,y, 'o', label='manual')
plt.axline((x1,y1), slope=sl, color='red', label='axline')
plt.legend()
plt.grid()
plt.show()

Related

Plotting arrows perpendicular to coordinates

I have a plot like this, plotting a semicircle with x and y
I want to add arrows at each point like so (ignore the horrible paint job):
Is there an easy way to add arrows perpendicular to the plot?
Current code:
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
y = k + np.sqrt(r**2 - (x-h)**2)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
PERPENDICULAR TO THE TANGENT OF THE CURVE I'M SORRY I FORGOT TO ADD THIS
A point in space has no idea what "perpendicular" means, but assuming your y is some function of x that has a derivate, you can think of the derivate of the function at some point to be the tangent of the curve at that point, and to get a perpendicular vector you just need to rotate the vector counter-clockwise 90 degrees:
x1, y1 = -y0, x0
We know that these points come from a circle. So given three points we can easily find the center using basic geometry notions. If you need a refresher, take a look here.
For this particular case, the center is at the origin. Knowing the center coordinates, the normal at each point is just the vector from the center to the point itself. Since the center is the origin, the normals' components are just given by the coordinates of the points themselves.
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
center = np.array([0.0, 0.0])
plt.scatter(x, y)
plt.quiver(x, y, x, y, width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
If you are in a hurry and you do not have time to implement equations, you could use the scikit-spatial library in the following way:
from skspatial.objects import Circle, Vector, Points
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
points = Points(np.vstack((x, y)).T)
circle = Circle.best_fit(np.vstack((x, y)).T)
center = circle.point
normals = np.array([Vector.from_points(center, point) for point in points])
plt.scatter(x, y)
plt.quiver(x, y, normals[:, 0], normals[:, 1], width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
Postulate of blunova's and simon's answers is correct, generally speaking: points have no normal, but curve have; so you need to rely on what you know your curve is. Either, as blunova described it, by the knowledge that it is a circle, and computing those normal with ad-hoc computation from that knowledge.
Or, as I am about to describe, using the function f such as y=f(x). and using knowledge on what is the normal to such a (x,f(x)) chart.
Here is your code, written with such a function f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
def f(x):
return k + np.sqrt(r**2 - (x-h)**2)
y=f(x)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
So, all I did here is rewriting your line y=... in the form of a function.
From there, it is possible to compute the normal to each point of the chart (x,f(x)).
The tangent to a point (x,f(x)) is well known: it is vector (1,f'(x)), where f'(x) is the derivative of f. So, normal to that is (-f'(x), 1).
Divided by √(f'(x)²+1) to normalize this vector.
So, just use that as entry to quiver.
First compute a derivative of your function
dx=0.001
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
Then, just
plt.quiver(x, f(x), -fprime(x), 1)
Or, to have all vector normalized
plt.quiver(x, f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
(note that fprime and the normalization part are all vectorizable operation, so it works with x being a arange)
All together
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return k+ np.sqrt(r**2 - (x-h)**2)
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-4,4)
plt.show()
That is almost an exact copy of your code, but for the quiver line, and with the addition of fprime.
One other slight change, specific to your curve, is that I changed x range to ensure the computability of fprime (if first x is x0, then fprime need f(x0-dx) which does not exist because of sqrt. Likewise for x1. So, first x is x0+dx, and last is x1-dx, which is visually the same)
That is the main advantage of this solution over blunova's: it is your code, essentially. And would work if you change f, without assuming that f is a circle. All that is assume is that f is derivable (and if it were not, you could not define what those normal are anyway).
For example, if you want to do the same with a parabola instead, just change f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return x**2
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-2,5)
plt.show()
All I changed here is the f formula. Not need for a new reasoning to compute the normal.
Last remark: an even more accurate version (not forcing the approximate computation of fprime with a dx) would be to use sympy to define f, and then compute the real, symbolic, derivative of f. But that doesn't seem necessary for your case.

How can I generate a random point (x, y) 10 steps apart from y0(a, b) in xy-plane?

I have generated a random point named y0=(a,b) in xy-plane , How can I generate another random point (x,y) 10 steps apart from y0?
note: by 10 steps apart from the firt point I don't mean the Euclidean distance. I mean the number of steps on lattice between the two point (a,b) and (x,y) which is given by |x-a|+|y-b|=10
My attempt(sometimes gives wrong result).
import random
y0=(random.randint(0,50),random.randint(0,50))# here I generated the first point.
y=random.randint(0,50)
# I used the formula |x-a|+|y-b|=10.
x=(10 -abs(y-y0[1]))+y0[0] or x=-(10 -abs(y-y0[1]))+y0[0]
x0=(x,y)
Let's say you have a point (x, y)
create another random point anywhere on the plane: (x1, y2) = (random(), random())
take the vector from your point to the new point: (vx, vy) = (x1-x, y1-y)
get the length l of the vector: l = sqrt(vx * vx + vy * vy)
use l to normalise the vector (so it has a length of 1): (vx, vy) = (vx / l, vy / l)
make the vector 10 steps long: (vx, vy) = (vx * 10, vy * 10)
add it to your original point to get to the desired point: (x1, y2) = (x + vx, y + vy)
voilá :)
from random import random
from math import sqrt
# Deviation
dev = 50
# Required distance between points
l = 10
if __name__ == '__main__':
# First random point
x0, y0 = dev*random(), dev*random()
# Second point
x1 = dev*random()
y1 = y0 + sqrt(l**2 - (x1 - x0)**2)
# Output
print "First point (%s, %s)" % (x0, y0)
print "Second point (%s, %s)" % (x1, y1)
print "Distance: %s" % (sqrt((x1 - x0)**2 + (y1 - y0)**2))
Let's say that your new point (x, y) is on a cercle of radius 10 and center (x0, y0). The random component is the angle.
import math as m
# radius of the circle
r = 10
# create random angle and compute coordinates of the new point
theta = 2*m.pi*random.random()
x = x0 + r*m.cos(theta)
y = y0 + r*m.sin(theta)
# test if the point created is in the domain [[0,50], [0, 50]] (see comments of PM2Ring)
while not ( 0<=x<=50 and 0<=y<=50 ) :
# update theta: add pi/2 until the new point is in the domain (see HumanCatfood's comment)
theta += 0.5*m.pi
x = x0 + r*m.cos(theta)
y = y0 + r*m.sin(theta)
So, you got the formula d=d1+d2=|x-x0|+|y-y0| , for d=10
Let's examine what's going on with this formula:
Let's say we generate a random point P at (0,0)
Let's say we generate y=random.randint(0,50) and let's imagine the value is 50.
What does this mean?
d1=|x-p[0]|=50 and your original formula is d=d1+d2=|x-x0|+|y-y0|, so
that means d2=|y-y0|=10-50 and d2=|y-y0|=-40. Is this possible? Absolutely not! An absolute value |y-y0| will always be positive, that's why your formula won't work for certain random points, you need to make sure (d-d1)>0, otherwise your equation won't have solution.
If you wanted to consider Euclidean distance you just need to generate random points in a circle where your original point will be the center, something like this will do:
import random
import math
def random_point(p, r=10):
theta = 2 * math.pi * random.random()
return (p[0] + r * math.cos(theta), p[1] + r * math.sin(theta))
If you draw a few random points you'll see more and more how the circle shape is created, let's try with N=10, N=50, N=1000:
Now, it seems you need the generated circle to be constrained at certain area region. One possible choice (not the most optimal though) would be generating random points till they meet those constraints, something like this would do:
def random_constrained_point(p, r=10, x_limit=50, y_limit=50):
i = 0
MAX_ITERATIONS = 100
while True:
x0, y0 = random_point(p, r)
if (0 <= x0 <= x_limit and 0 <= y0 <= y_limit):
return (x0, y0)
if i == MAX_ITERATIONS:
return p
i += 1
Once you got this, it's interesting to check what shape is created when you increase more and more the circle radius (10,20,50):
As you can see, your generated random constrained points will form a well_defined subarc.
this code generate a random point xy-plane named y0 then generate another point x0 10 steps apart from y0 in taxi distance .
------- begining of the code--------
import random
y0=(random.randint(0,50),random.randint(0,50))
while True:
y=random.randint(0,50)
x=(10 -abs(y-y0[1]))+y0[0]
if (abs(x-y0[0])+abs(y-y0[1]))==10:
x0=(x,y)
break
abs(x)+abs(y)=10 defines a square, so all you need to do is pick a random value along the perimeter of the square (40 units long), and map that random distance back to your x,y coordinate pair.
Something like (untested):
x = random.randint(-10,9)
y = 10 - abs(x)
if (random.randint(0,1) == 0):
x = -x
y = -y
x = x + y0[0]
y = y + y0[1]
x0=(x,y)
Clipping the x range that way ensures that all points are picked uniformly. Otherwise you can end up with (-10,0) and (10,0) having twice the chance of being picked compared to any other coordinate.

How to extract a 1d profile (with integrated width) from a 2D array in an arbitrary direction

i have the following problem: I would like to extract a 1D profile from a 2D array, which is relatively simple. And it is also easy to do this in an arbitrary direction (see here).
But i would like to give the profile a certain width, so that the values perpendicular to the profile are averaged. I managed to do this, but it is extremely slow.
Does anyone have a good solution for that?
Thanks!
import numpy as np
import os
import math
import itertools
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
def closest_point(points, coords):
min_distances = []
coords = coords
for point in points:
distances = []
for coord in coords:
distances.append(np.sqrt((point[0]-coord[0])**2 + (point[1]-coord[1])**2))
val, idx = min((val, idx) for (idx, val) in enumerate(distances))
min_distances.append(coords[idx])
return min_distances
def rect_profile(x0, y0, x1, y1, width):
xd=x1-x0
yd=y1-y0
alpha = (np.angle(xd+1j*yd))
y00 = y0 - np.cos(math.pi - alpha)*width
x00 = x0 - np.sin(math.pi - alpha)*width
y01 = y0 + np.cos(math.pi - alpha)*width
x01 = x0 + np.sin(math.pi - alpha)*width
y10 = y1 + np.cos(math.pi - alpha)*width
x10 = x1 + np.sin(math.pi - alpha)*width
y11 = y1 - np.cos(math.pi - alpha)*width
x11 = x1 - np.sin(math.pi - alpha)*width
vertices = ((y00, x00), (y01, x01), (y10, x10), (y11, x11))
poly_points = [x00, x01, x10, x11], [y00, y01, y10, y11]
poly = Polygon(((y00, x00), (y01, x01), (y10, x10), (y11, x11)))
return poly, poly_points
def averaged_profile(image, x0, y0, x1, y1, width):
num = np.sqrt((x1-x0)**2 + (y1-y0)**2)
x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num)
coords = list(zip(x, y))
# Get all points that are in Rectangle
poly, poly_points = rect_profile(x0, y0, x1, y1, width)
points_in_poly = []
for point in itertools.product(range(image.shape[0]), range(image.shape[1])):
if poly.get_path().contains_point(point, radius=1) == True:
points_in_poly.append((point[1], point[0]))
# Finds closest point on line for each point in poly
neighbour = closest_point(points_in_poly, coords)
# Add all phase values corresponding to closest point on line
data = []
for i in range(len(coords)):
data.append([])
for idx in enumerate(points_in_poly):
index = coords.index(neighbour[idx[0]])
data[index].append(image[idx[1][1], idx[1][0]])
# Average data perpendicular to profile
for i in enumerate(data):
data[i[0]] = np.nanmean(data[i[0]])
# Plot
fig, axes = plt.subplots(figsize=(10, 5), nrows=1, ncols=2)
axes[0].imshow(image)
axes[0].plot([poly_points[0][0], poly_points[0][1]], [poly_points[1][0], poly_points[1][1]], 'yellow')
axes[0].plot([poly_points[0][1], poly_points[0][2]], [poly_points[1][1], poly_points[1][2]], 'yellow')
axes[0].plot([poly_points[0][2], poly_points[0][3]], [poly_points[1][2], poly_points[1][3]], 'yellow')
axes[0].plot([poly_points[0][3], poly_points[0][0]], [poly_points[1][3], poly_points[1][0]], 'yellow')
axes[0].axis('image')
axes[1].plot(data)
return data
from scipy.misc import face
img = face(gray=True)
profile = averaged_profile(img, 10, 10, 500, 500, 10)
As another option, There's now a scipy measure function that does exactly this (get profile between arbitrary points in a 2d array, with optional width specified):skimage.measure.profile_line.
As a big plus, it also lets you specify the interpolation value to use for off-grid locations.
I'm not sure how it compares to the above code though - I know for orthogonal cases it is much much faster (ie order magnitude or more) to use simple array slicing/summing.
Like heltonbiker says, if you're really needing speed (large array and/or many times) it is faster to first rotate the matrix, then just use slicing. A technique I've used in the past is basically his approach, but also first essentially masking the original unrotated array, and then only rotating the portion of the array that is the size of the area of your profile (plus a bit).
The downside with that approach (for speed) is that for the rotation you need to use some form of interpolation, which is generally slow, and to get accurate results you need at least linear (order 1) interpolation. However most of the python library modules (there are at least 3) for array rotation seem fairly optimised.
...However for pure convenience, then profile_line is the way to go
The main performance hog is the function closest_point. Computing the distances between all points on the line with all points in the rectangle is really slow.
You can speed the function up considerably by projecting all rectangle points onto the line. The projected point is the closest point on the line, so there is no need for computing all distances. Further, by correctly normalizing and rounding the projection (distance from line start) it can be directly used as an index.
def closest_point(points, x0, y0, x1, y1):
line_direction = np.array([x1 - x0, y1 - y0], dtype=float)
line_length = np.sqrt(line_direction[0]**2 + line_direction[1]**2)
line_direction /= line_length
n_bins = int(np.ceil(line_length))
# project points on line
projections = np.array([(p[0] * line_direction[0] + p[1] * line_direction[1]) for p in points])
# normalize projections so that they can be directly used as indices
projections -= np.min(projections)
projections *= (n_bins - 1) / np.max(projections)
return np.floor(projections).astype(int), n_bins
If you wonder about the strange for inside brackets - these are list comprehensions.
Use the function like this inside averaged_profile:
#...
# Finds closest point on line for each point in poly
neighbours, n_bins = closest_point(points_in_poly, x0, y0, x1, y1)
# Add all phase values corresponding to closest point on line
data = [[] for _ in range(n_bins)]
for idx in enumerate(points_in_poly):
index = neighbours[idx[0]]
data[index].append(image[idx[1][1], idx[1][0]])
#...
This optimization will make the computation noticably faster. If it is still too slow for you, you can also optimize how you find the points inside the polygon. Instead of testing if each point in the image is inside the rectangle you can use a polygon rasterization algorithm to directly generate the coordinates. See here for details.
Finally, although it is not a performance issue, the use of complex numbers to compute an angle is very creative :)
Instead of trigonometric functions you can use the fact that the normal vector of the line is [yd, -xd] devided by line length:
def rect_profile(x0, y0, x1, y1, width):
xd = x1 - x0
yd = y1 - y0
length = np.sqrt(xd**2 + yd**2)
y00 = y0 + xd * width / length
x00 = x0 - xd * width / length
y01 = y0 - xd * width / length
x01 = x0 + xd * width / length
y10 = y1 - xd * width / length
x10 = x1 + xd * width / length
y11 = y1 + xd * width / length
x11 = x1 - xd * width / length
poly_points = [x00, x01, x10, x11], [y00, y01, y10, y11]
poly = Polygon(((y00, x00), (y01, x01), (y10, x10), (y11, x11)))
return poly, poly_points
I would do the following:
Find out the direction of the desired line, and thus its angle;
Rotate the 2D array via matrix multiplication, using a rotation matrix with the angle you found;
Do a simple bounding box filtering with a rectangle representing your selected area. This will by definition be aligned to one of the axes;
Discard the y coordinate of the points inside the bounding box;
Smooth the results, possibly via 1D spline interpolation (available in scipy).

Draw rounded fancyarrowpatch with midpoint arrow in matplotlib

I've been trying to push the boundaries of matplotlib's patches and instruct it to draw a rounded FancyArrowPatch with a directional arrow on its midpoint. This would prove incredibly useful in a network representation I am trying to create.
My coding hours with python are not yet in the double digit, so I can't say I have a clear understanding of matplotlib's patches.py, but I have narrowed down the solution to two possible strategies:
the smart, possibly pythonic way: create a custom arrowstyle class which further requires a modification of the _get_arrow_wedge() function to include a midpoint coordinates. This may be beyond my possibilities for now, or
the lazy way: extract the midpoint coordinates from an elicited FancyArrowPatch and draw the desired arrowstyle on such coordinates.
Of course, so far I've chosen the lazy way. I did some early experimenting with extracting the midpoint coordinates of a curved FancyArrowPatch using get_path() and get_path_in_displaycoord(), but I can't seem to predict the precise midpoint coordinates. Some help would be very appreciated.
My fiddling so far:
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
n1 = (2,3)
n2 = (4,6)
# Try with multiple arc radius sizes, draw a separate plot each time
for rad in range(20):
#setup figure
figure = plt.figure()
ax = plt.subplot(111)
plt.annotate('rad:' + str(rad/25.),xy=(2,5))
# create rounded fancyarrowpatch
t = FancyArrowPatch(posA=n1,posB=n2,
connectionstyle='arc3,rad=%s'%float(rad/25.),
arrowstyle='->',
shrinkA=0,
shrinkB=0,
mutation_scale=0.5)
# extract vertices from get_path: points P#
path = t.get_path().vertices.tolist()
lab, px, py = ['P{0}'.format(i) for i in range(len(path))], [u[0] for u in path],[u[1] for u in path]
for i in range(len(path)):
plt.annotate(lab[i],xy=(px[i],py[i]))
# extract vertices from get_path_in_displaycoord (but they are useless) : points G#
newpath = t.get_path_in_displaycoord()
a,b = newpath[0][0].vertices.tolist(), newpath[0][1].vertices.tolist()
a.extend(b)
glab, gx, gy = ['G{0}'.format(i) for i in range(len(a))], [u[0] for u in a],[u[1] for u in a]
for i in range(len(a)):
plt.annotate(glab[i],xy=(gx[i],gy[i]))
#point A: start
x1, y1 = n1
plt.annotate('A',xy=(x1,y1))
#point B:end
x2, y2 = n2
plt.annotate('B',xy=(x2,y2))
#point M: the 'midpoint' as defined by class Arc3, specifically its connect() function
x12, y12 = (x1 + x2) / 2., (y1 + y2) / 2.
dx, dy = x2 - x1, y2 - y1
cx, cy = x12 + (rad/100.) * dy, y12 - (rad/100.) * dx
plt.annotate('M',xy=(cx,cy))
#point O : midpoint between M and P1, the second vertex from get_path
mx,my = (cx + px[1])/2., (cy + py[1])/2.
plt.annotate('O',xy=(mx,my))
ax.add_patch(t)
plt.scatter([x1,cx,x2,mx,gx].extend(px),[y1,cy,y2,my,gy].extend(py))
plt.show()
EDIT: taking onboard #cphlewis suggestions: I tried to reconstruct the Bezier curve:
def bezcurv(start,control,end,tau):
ans = []
for t in tau:
B = [(1-t)**2 * start[i] + 2*(1-t)*t*end[i] + (t**2)*control[i] for i in range(len(start))]
ans.append(tuple(B))
return ans
I thus add the generated line to the original plot:
tau = [time/100. for time in range(101)]
bezsim = bezcurv(n1,n2,(cx,cy),tau)
simx,simy = [b[0] for b in bezsim], [b[1] for b in bezsim]
The green line below is (should be?) the reconstructed bezier curve, though it's clearly not.
After much struggling, I convinced myself that to solve this I had to part away from the FancyArrowPatch suite and create something from scratch. Here is a working solution that, far from fulfilling any perfectionist spirit, satisfied me:
import matplotlib.pyplot as plt
import numpy as np
from numpy.random import seed, randint
# Build function that connects two points with a curved line,
# and an arrow on the middle of it
seed(1679)
narrow = 3
rad_one = 50
numpoints = 3
random_points = list(randint(1,20,[numpoints,4]))
rpoints = [[(a,b),(c,d)] for a,b,c,d in random_points]
def curvline(start,end,rad,t=100,arrows=1,push=0.8):
#Compute midpoint
rad = rad/100.
x1, y1 = start
x2, y2 = end
y12 = (y1 + y2) / 2
dy = (y2 - y1)
cy = y12 + (rad) * dy
#Prepare line
tau = np.linspace(0,1,t)
xsupport = np.linspace(x1,x2,t)
ysupport = [(1-i)**2 * y1 + 2*(1-i)*i*cy + (i**2)*y2 for i in tau]
#Create arrow data
arset = list(np.linspace(0,1,arrows+2))
c = zip([xsupport[int(t*a*push)] for a in arset[1:-1]],
[ysupport[int(t*a*push)] for a in arset[1:-1]])
dt = zip([xsupport[int(t*a*push)+1]-xsupport[int(t*a*push)] for a in arset[1:-1]],
[ysupport[int(t*a*push)+1]-ysupport[int(t*a*push)] for a in arset[1:-1]])
arrowpath = zip(c,dt)
return xsupport, ysupport, arrowpath
def plotcurv(start,end,rad,t=100,arrows=1,arwidth=.25):
x, y, c = curvline(start,end,rad,t,arrows)
plt.plot(x,y,'k-')
for d,dt in c:
plt.arrow(d[0],d[1],dt[0],dt[1], shape='full', lw=0,
length_includes_head=False, head_width=arwidth)
return c
#Create figure
figure = plt.figure()
ax = plt.subplot(111)
for n1,n2 in rpoints:
#First line
plotcurv(n1,n2,rad_one,200,narrow,0.5)
#Second line
plotcurv(n2,n1,rad_one,200,narrow,0.5)
ax.set_xlim(0,20)
ax.set_ylim(0,20)
plt.show
I have tested it with three random couple of points, plotting back and forth lines. Which gives the figure below:
The function allows for the user to set a number of desired arrow-heads, and it places them evenly on the plotted Bezier, making sure the appropriate direction is represented. However, because the Bezier curve is not exactly an 'arc', I heuristically push the start of the arrow-heads to make them look more centered. Any improvement to this solution will be greatly appreciated.

Python calculate point of intersection of two great circles

I am trying to calculate the point of intersection (lat and lon in degrees) of two great circles that are each defined by two points on the circle. I have been trying to follow method outlined here.
But the answer I get is incorrect, my code is below does anyone see where I went wrong?
import numpy as np
from numpy import cross
from math import cos, sin, atan2, asin, asinh
################################################
#### Intersection of two great circles.
# Points on great circle 1.
glat1 = 54.8639587
glon1 = -8.177818
glat2 = 52.65297082
glon2 = -10.78064876
# Points on great circle 2.
cglat1 = 51.5641564
cglon1 = -9.2754284
cglat2 = 53.35422063
cglon2 = -12.5767799
# 1. Put in polar coords.
x1 = cos(glat1) * sin(glon1)
y1 = cos(glat1) * cos(glon1)
z1 = sin(glat1)
x2 = cos(glat2) * sin(glon2)
y2 = cos(glat2) * cos(glon2)
z2 = sin(glat2)
cx1 = cos(cglat1) * sin(cglon1)
cy1 = cos(cglat1) * cos(cglon1)
cz1 = sin(cglat1)
cx2 = cos(cglat2) * sin(cglon2)
cy2 = cos(cglat2) * cos(cglon2)
cz2 = sin(cglat2)
# 2. Get normal to planes containing great circles.
# It's the cross product of vector to each point from the origin.
N1 = cross([x1, y1, z1], [x2, y2, z2])
N2 = cross([cx1, cy1, cz1], [cx2, cy2, cz2])
# 3. Find line of intersection between two planes.
# It is normal to the poles of each plane.
L = cross(N1, N2)
# 4. Find intersection points.
X1 = L / abs(L)
X2 = -X1
ilat = asin(X1[2]) * 180./np.pi
ilon = atan2(X1[1], X1[0]) * 180./np.pi
I should also mention this is on the Earth's surface (assuming a sphere).
Solution from DSM in comments above, your angles are in degrees while sin and cos expect radians.
Also the line
X1 = L / abs(L)
should be,
X1 = L / np.sqrt(L[0]**2 + L[1]**2 + L[2]**2)
One more correction that needs to be done is to change cos/sin before "lon" in x and y dimensions:
x = cos(lat) * cos(lon)
y = cos(lat) * sin(lon)
z = sin(lat)
This is because original conversion from angle to spherical system is done using polar/azimuthal sphere angles and they are not the same as lat/lon angles (wiki it https://en.wikipedia.org/wiki/Spherical_coordinate_system).

Categories

Resources