Isochrones using OSMnx - geospatial

I am quite new to geospatial data. In my work, I have to calculate the polygon using isochrones. The Data on which I am working is quite large(1.2 million rows). To execute each row, it takes around 1 sec, so by that calculation, it will take around 13days to run this code!. Is there any way to reduce the time?
def get_isochrone(lon, lat, count, walk_time=10, speed=4.5):
loc = (lat, lon)
G = ox.graph_from_point(loc, simplify=True, network_type='walk')
#Create nodes geodataframe from Graph network (G)
gdf_nodes = ox.graph_to_gdfs(G, edges=False)
x, y = gdf_nodes['geometry'].unary_union.centroid.xy
# center_node = ox.distance.nearest_nodes(G, x[0], y[0])
center_node = ox.get_nearest_node(G, (y[0], x[0]))
meters_per_minute = speed * 1000 / 60
for u, v, k, data in G.edges(data=True, keys=True):
data['time'] = data['length'] / meters_per_minute
subgraph = nx.ego_graph(G, center_node, radius=walk_time, distance='time')
node_points = [Point(data['x'], data['y']) for node, data in subgraph.nodes(data=True)]
polys = gpd.GeoSeries(node_points).unary_union.convex_hull
if (count%5)==0:
print(count)
return polys
wsp_df['polygon'] = wsp_df.apply(lambda x: get_isochrone(x.lon, x.lat, x.counter ), axis=1)

Related

Speed Up a for Loop - Python

I have a code that works perfectly well but I wish to speed up the time it takes to converge. A snippet of the code is shown below:
def myfunction(x, i):
y = x + (min(0, target[i] - data[i, :]x))*data[i]/(norm(data[i])**2))
return y
rows, columns = data.shape
start = time.time()
iterate = 0
iterate_count = []
norm_count = []
res = 5
x_not = np.ones(columns)
norm_count.append(norm(x_not))
iterate_count.append(0)
while res > 1e-8:
for row in range(rows):
y = myfunction(x_not, row)
x_not = y
iterate += 1
iterate_count.append(iterate)
norm_count.append(norm(x_not))
res = abs(norm_count[-1] - norm_count[-2])
print('Converge at {} iterations'.format(iterate))
print('Duration: {:.4f} seconds'.format(time.time() - start))
I am relatively new in Python. I will appreciate any hint/assistance.
Ax=b is the problem we wish to solve. Here, 'A' is the 'data' and 'b' is the 'target'
Ugh! After spending a while on this I don't think it can be done the way you've set up your problem. In each iteration over the row, you modify x_not and then pass the updated result to get the solution for the next row. This kind of setup can't be vectorized easily. You can learn the thought process of vectorization from the failed attempt, so I'm including it in the answer. I'm also including a different iterative method to solve linear systems of equations. I've included a vectorized version -- where the solution is updated using matrix multiplication and vector addition, and a loopy version -- where the solution is updated using a for loop to demonstrate what you can expect to gain.
1. The failed attempt
Let's take a look at what you're doing here.
def myfunction(x, i):
y = x + (min(0, target[i] - data[i, :] # x)) * (data[i] / (norm(data[i])**2))
return y
You subtract
the dot product of (the ith row of data and x_not)
from the ith row of target,
limited at zero.
You multiply this result with the ith row of data divided my the norm of that row squared. Let's call this part2
Then you add this to the ith element of x_not
Now let's look at the shapes of the matrices.
data is (M, N).
target is (M, ).
x_not is (N, )
Instead of doing these operations rowwise, you can operate on the entire matrix!
1.1. Simplifying the dot product.
Instead of doing data[i, :] # x, you can do data # x_not and this gives an array with the ith element giving the dot product of the ith row with x_not. So now we have data # x_not with shape (M, )
Then, you can subtract this from the entire target array, so target - (data # x_not) has shape (M, ).
So far, we have
part1 = target - (data # x_not)
Next, if anything is greater than zero, set it to zero.
part1[part1 > 0] = 0
1.2. Finding rowwise norms.
Finally, you want to multiply this by the row of data, and divide by the square of the L2-norm of that row. To get the norm of each row of a matrix, you do
rownorms = np.linalg.norm(data, axis=1)
This is a (M, ) array, so we need to convert it to a (M, 1) array so we can divide each row. rownorms[:, None] does this. Then divide data by this.
part2 = data / (rownorms[:, None]**2)
1.3. Add to x_not
Finally, we're adding each row of part1 * part2 to the original x_not and returning the result
result = x_not + (part1 * part2).sum(axis=0)
Here's where we get stuck. In your approach, each call to myfunction() gives a value of part1 that depends on target[i], which was changed in the last call to myfunction().
2. Why vectorize?
Using numpy's inbuilt methods instead of looping allows it to offload the calculation to its C backend, so it runs faster. If your numpy is linked to a BLAS backend, you can extract even more speed by using your processor's SIMD registers
The conjugate gradient method is a simple iterative method to solve certain systems of equations. There are other more complex algorithms that can solve general systems well, but this should do for the purposes of our demo. Again, the purpose is not to have an iterative algorithm that will perfectly solve any linear system of equations, but to show what kind of speedup you can expect if you vectorize your code.
Given your system
data # x_not = target
Let's define some variables:
A = data.T # data
b = data.T # target
And we'll solve the system A # x = b
x = np.zeros((columns,)) # Initial guess. Can be anything
resid = b - A # x
p = resid
while (np.abs(resid) > tolerance).any():
Ap = A # p
alpha = (resid.T # resid) / (p.T # Ap)
x = x + alpha * p
resid_new = resid - alpha * Ap
beta = (resid_new.T # resid_new) / (resid.T # resid)
p = resid_new + beta * p
resid = resid_new + 0
To contrast the fully vectorized approach with one that uses iterations to update the rows of x and resid_new, let's define another implementation of the CG solver that does this.
def solve_loopy(data, target, itermax = 100, tolerance = 1e-8):
A = data.T # data
b = data.T # target
rows, columns = data.shape
x = np.zeros((columns,)) # Initial guess. Can be anything
resid = b - A # x
resid_new = b - A # x
p = resid
niter = 0
while (np.abs(resid) > tolerance).any() and niter < itermax:
Ap = A # p
alpha = (resid.T # resid) / (p.T # Ap)
for i in range(len(x)):
x[i] = x[i] + alpha * p[i]
resid_new[i] = resid[i] - alpha * Ap[i]
# resid_new = resid - alpha * A # p
beta = (resid_new.T # resid_new) / (resid.T # resid)
p = resid_new + beta * p
resid = resid_new + 0
niter += 1
return x
And our original vector method:
def solve_vect(data, target, itermax = 100, tolerance = 1e-8):
A = data.T # data
b = data.T # target
rows, columns = data.shape
x = np.zeros((columns,)) # Initial guess. Can be anything
resid = b - A # x
resid_new = b - A # x
p = resid
niter = 0
while (np.abs(resid) > tolerance).any() and niter < itermax:
Ap = A # p
alpha = (resid.T # resid) / (p.T # Ap)
x = x + alpha * p
resid_new = resid - alpha * Ap
beta = (resid_new.T # resid_new) / (resid.T # resid)
p = resid_new + beta * p
resid = resid_new + 0
niter += 1
return x
Let's solve a simple system to see if this works first:
2x1 + x2 = -5
−x1 + x2 = -2
should give a solution of [-1, -3]
data = np.array([[ 2, 1],
[-1, 1]])
target = np.array([-5, -2])
print(solve_loopy(data, target))
print(solve_vect(data, target))
Both give the correct solution [-1, -3], yay! Now on to bigger things:
data = np.random.random((100, 100))
target = np.random.random((100, ))
Let's ensure the solution is still correct:
sol1 = solve_loopy(data, target)
np.allclose(data # sol1, target)
# Output: False
sol2 = solve_vect(data, target)
np.allclose(data # sol2, target)
# Output: False
Hmm, looks like the CG method doesn't work for badly conditioned random matrices we created. Well, at least both give the same result.
np.allclose(sol1, sol2)
# Output: True
But let's not get discouraged! We don't really care if it works perfectly, the point of this is to demonstrate how amazing vectorization is. So let's time this:
import timeit
timeit.timeit('solve_loopy(data, target)', number=10, setup='from __main__ import solve_loopy, data, target')
# Output: 0.25586539999994784
timeit.timeit('solve_vect(data, target)', number=10, setup='from __main__ import solve_vect, data, target')
# Output: 0.12008900000000722
Nice! A ~2x speedup simply by avoiding a loop while updating our solution!
For larger systems, this will be even better.
for N in [10, 50, 100, 500, 1000]:
data = np.random.random((N, N))
target = np.random.random((N, ))
t_loopy = timeit.timeit('solve_loopy(data, target)', number=10, setup='from __main__ import solve_loopy, data, target')
t_vect = timeit.timeit('solve_vect(data, target)', number=10, setup='from __main__ import solve_vect, data, target')
print(N, t_loopy, t_vect, t_loopy/t_vect)
This gives us:
N t_loopy t_vect speedup
00010 0.002823 0.002099 1.345390
00050 0.051209 0.014486 3.535048
00100 0.260348 0.114601 2.271773
00500 0.980453 0.240151 4.082644
01000 1.769959 0.508197 3.482822

Different shape arrays operations

A bit of background:
I want to calculate the array factor of a MxN antenna array, which is given by the following equation:
Where w_i are the complex weight of the i-th element, (x_i,y_i,z_i) is the position of the i-th element, k is the wave number, theta and phi are the elevation and azimuth respectively, and i ranges from 0 to MxN-1.
In the code I have:
-theta and phi are np.mgrid with shape (200,200) each,
-w_i, and (x,y,z)_i are np.array with shape (NxM,) each
so AF is a np.array with shape (200,200) (sum over i).There is no problem so far, and I can get AF easily doing:
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
Now, each w_i depends on frequency, so AF too, and now I have w_i with shape (NxM,1000) (I have 1000 samples of each w_i in frequency). I tried to use the above code changing
af = zeros([1000,theta.shape[0],phi.shape[0]])
but I get 'operands could not be broadcast together'. I can solve this by using a for loop through the 1000 values, but it is slow and is a bit ugly. So, what is the correct way to do the summation, or the correct way to properly define w_i and AF ?
Any help would be appreciated. Thanks.
edit
The code with the new dimension I'm trying to add is the next:
from numpy import *
class AntennaArray:
def __init__(self,f,asize=None,tipo=None,dx=None,dy=None):
self.Lambda = 299792458 / f
self.k = 2*pi/self.Lambda
self.size = asize
self.type = tipo
self._AF_DATA_SIZE = 200
self.theta,self.phi = mgrid[0 : pi : self._AF_DATA_SIZE*1j,0 : 2*pi : self._AF_DATA_SIZE*1j]
self.element_pos = None
self.element_amp = None
self.element_pha = None
if dx == None:
self.dx = self.Lambda/2
else:
self.dx = dx
if dy == None:
self.dy = self.Lambda/2
else:
self.dy = dy
self.generate_array()
def generate_array(self):
M = self.size[0]
N = self.size[1]
dx = self.dx
dy = self.dy
x_pos = arange(0,dx*N,dx)
y_pos = arange(0,dy*M,dy)
z_pos = 0
ele = zeros([N*M,3])
for i in range(M):
ele[i*N:(i+1)*N,0] = x_pos[:]
for i in range(M):
ele[i*N:(i+1)*N,1] = y_pos[i]
self.element_pos = ele
#self.array_factor = self.calculate_array_factor()
def calculate_array_factor(self):
theta,phi = self.theta,self.phi
k = self.k
x_pos = self.element_pos[:,0]
y_pos = self.element_pos[:,1]
z_pos = self.element_pos[:,2]
w = self.element_amp*exp(1j*self.element_pha)
if len(self.element_pha.shape) > 1:
#I have f_size samples of w_i(f)
f_size = self.element_pha.shape[1]
af = zeros([f_size,theta.shape[0],phi.shape[0]])
else:
#I only have w_i
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
**strong text**#This for loop does the summation over i
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
return af
I tried to test it with the next main
from numpy import *
f_points = 10
M = 2
N = 2
a = AntennaArray(5.8e9,[M,N])
a.element_amp = ones([M*N,f_points])
a.element_pha = zeros([M*N,f_points])
af = a.calculate_array_factor()
But I get
ValueError: 'operands could not be broadcast together with shapes (10,) (200,200) '
Note that if I set
a.element_amp = ones([M*N])
a.element_pha = zeros([M*N])
This works well.
Thanks.
I had a look at the code, and I think this for loop:
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
is wrong in many ways. You are mixing dimensions, you cannot loop that way.
And by the way, to make full use of numpy efficiency, never loop over the arrays. It slows down the execution significantly.
I tried to rework that part.
First, I advice you to not use from numpy import *, it's bad practice (see here). Use import numpy as np. I reintroduced the np abbreviation, so you can understand what comes from numpy.
Frequency independent case
This first snippet assumes that w is a 1D array of length 4: I am neglecting the frequency dependency of w, to show you how you can get what you already obtained without the for loop and using instead the power of numpy.
af_points = w[:,np.newaxis,np.newaxis]*np.e**(-1j*
(k * x_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.cos(phi) +
k * y_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.sin(phi) +
k * z_pos[:,np.newaxis,np.newaxis]*np.cos(theta)
))
af = np.sum(af_points, axis=0)
I am using numpy broadcasting to obtain a 3D array named af_points, whose shape is (4, 200, 200). To do it, I use np.newaxis to extend the number of axis of an array in order to use broadcasting correctly. More here on np.newaxis.
So, w[:,np.newaxis,np.newaxis] is an array of shape (4, 1, 1). Similarly for x_pos[:,np.newaxis,np.newaxis], y_pos[:,np.newaxis,np.newaxis] and z_pos[:,np.newaxis,np.newaxis]. Since the angles have shape (200, 200), broadcasting can be done, and af_points has shape (4, 200, 200).
Finally the sum is done by np.sum, summing over the first axis to obtain a (200, 200) array.
Frequency dependent case
Now w has shape (4, 10), where 10 are the frequency points. The idea is the same, just consider that the frequency is an additional dimension in your numpy arrays: now af_points will be an array of shape (4, 10, 200, 200) where 10 are the f_points you have defined.
To keep it understandable, I've split the calculation:
#exp_point is only the exponent, frequency independent. Will be a (4, 200, 200) array.
exp_points = np.e**(-1j*
(k * x_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.cos(phi) +
k * y_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.sin(phi) +
k * z_pos[:,np.newaxis,np.newaxis]*np.cos(theta)
))
af_points = w[:,:,np.newaxis,np.newaxis] * exp_points[:,np.newaxis,:,:]
af = np.sum(af_points, axis=0)
And now af has shape (10, 200, 200).

How to execute a function and save in a new csv file?

This program converts coordinates. What I am trying to do is to
use a csv file as input
use the function to convert the coordinates
save the output as a new csv file.
My file (worksheet.csv) has three columns, latitude, longitude and height.
How would I approach this?
import math
import csv
# semi-major axis of earth
a = 6378137.0
# 1/f is reciprocal of flatteing
f= 0.00335281068
# converts the input from degree to radians
latitude = math.radians(float(input('Enter Latitude:')))
longitude = math.radians(float(input('Enter Longitude:')))
height = float(input('Enter Height:'))
def earthConverter(latitude, longitude, height):
e = math.sqrt((2 * f) - (f**2))
N = a / math.sqrt(1-e**2 * math.sin(latitude)**2)
x = (N + height) * math.cos(latitude) * math.cos(longitude)
y = (N + height) * math.cos(latitude) * math.sin(longitude)
z = (N * (1 - e**2 ) + height) * math.sin(latitude)
return x, y, z
############################################
with open('worksheet.csv', 'r') as csvFile:
reader = csv.reader(csvFile)
for row in reader:
writer = csv.writer(csvFile)
writer.writerow(row[0], row[1], row[2], earthConverter(math.radians(float(row[0])),
earthConverter(math.radians(float(row[1])), earthConverter(float(row[2])) )
csvFile.close()
You've very close, but there are several things that need to be changed. Here's what I think is a full solution, but below I'll work through each part of the code
import math
import csv
def earthConverter(latitude, longitude, height):
f = 0.00335281068
a = 6378137.0
e = math.sqrt((2 * f) - (f**2))
N = a / math.sqrt(1-e**2 * math.sin(latitude)**2)
x = (N + height) * math.cos(latitude) * math.cos(longitude)
y = (N + height) * math.cos(latitude) * math.sin(longitude)
z = (N * (1 - e**2 ) + height) * math.sin(latitude)
return x, y, z
with open('worksheet.csv', 'r') as Infile, open('worksheet_out.csv', 'w') as Outfile:
reader = csv.reader(Infile)
# next(reader, None)
writer = csv.writer(Outfile)
for row in reader:
lat = math.radians(float(row[0]))
lon = math.radians(float(row[1]))
ht = math.radians(float(row[2]))
x, y, z = earthConverter(lat, lon, ht)
row_out = [row[0], row[1], row[2], x, y, z]
writer.writerow(row_out)
First, you can move the definitions of f and a into the earthConverter function itself to avoid any possible problems with variable scoping. This isn't strictly necessary.
Second, you can get rid of the latitude = math.radians(float(input('Enter Latitude:'))) lines. Those ask for user input, which is not what you want here.
Third, you cannot write back to the same csv. You've opened it in read mode ('r'), but even if you changed that, this post gives some details about why that won't work/is a bad idea. You can also get rid of the separate call to close the csv at the end of your code -- the with open() construction takes care of that for you.
Fourth, your earthConverter function returns a tuple, so you need to unpack those values somehow before trying to write them out again.
Everything in the for row in reader: block could be condensed into fewer rows. I broke it up this way because it makes it a little easier to read.
Also, you didn't mention whether your input csv had a header. If it does, then uncomment the line next(reader, None), which will skip the header. If you need to write a header out again, then you could change the for row in reader: block to this:
for i, row in enumerate(reader):
if i == 1:
header_out = ['lat', 'lon', 'ht', 'x', 'y', 'z'] # or whatever
writer.writerow(header_out)
lat = math.radians(float(row[0]))
lon = math.radians(float(row[1]))
ht = math.radians(float(row[2]))
x, y, z = earthConverter(lat, lon, ht)
row_out = [row[0], row[1], row[2], x, y, z]
writer.writerow(row_out)
All you have to do is create a Dataframe to read the csv file and create a for loop to iterate through each row so and insert it into a new Dataframe. Then we let the panda library Export it into a new csv file.
import pandas as pd
import math
# semi-major axis of earth
a = 6378137.0
# 1/f is reciprocal of flatteing
f = 0.00335281068
def earthConverter(latitude, longitude, height):
e = math.sqrt((2 * f) - (f**2))
N = a / math.sqrt(1-e**2 * math.sin(latitude)**2)
x = (N + height) * math.cos(latitude) * math.cos(longitude)
y = (N + height) * math.cos(latitude) * math.sin(longitude)
z = (N * (1 - e**2 ) + height) * math.sin(latitude)
return x, y, z
def new_csv(input_file, output_file):
df = pd.read_csv(input_file)
points_df = pd.DataFrame(columns=['Latitude', 'Longitude', 'Height'])
for i, row in df.iterrows():
x1, y1, z1 = earthConverter(row['Latitude'], row['Longitude'], row['Height'])
temp_df = pd.DataFrame({'Latitude': x1,
'Longitude': y1,
'Height': z1}, index=[0])
points_df = points_df.append(temp_df, ignore_index=True)
points_df.to_csv(output_file)
new_csv('worksheet.csv', 'new_worksheet.csv')

Weighted moving average in python with different width in different regions

I was trying to take a oscillation avarage of a highly oscillating data. The oscillations are not uniform, it has less oscillations in the initial regions.
x = np.linspace(0, 1000, 1000001)
y = some oscillating data say, sin(x^2)
(The original data file is huge, so I can't upload it)
I want to take a weighted moving avarage of the function and plot it. Initially the period of the function is larger, so I want to take avarage over a large time interval. While I can do with smaller time interval latter.
I have found a possible elegant solution in following post:
Weighted moving average in python
However, I want to have different width in different regions of x. Say when x is between (0,100) I want the width=0.6, while when x is between (101, 300) width=0.2 and so on.
This is what I have tried to implement( with my limited knowledge in programing!)
def weighted_moving_average(x,y,step_size=0.05):#change the width to control average
bin_centers = np.arange(np.min(x),np.max(x)-0.5*step_size,step_size)+0.5*step_size
bin_avg = np.zeros(len(bin_centers))
#We're going to weight with a Gaussian function
def gaussian(x,amp=1,mean=0,sigma=1):
return amp*np.exp(-(x-mean)**2/(2*sigma**2))
if x.any() < 100:
for index in range(0,len(bin_centers)):
bin_center = bin_centers[index]
weights = gaussian(x,mean=bin_center,sigma=0.6)
bin_avg[index] = np.average(y,weights=weights)
else:
for index in range(0,len(bin_centers)):
bin_center = bin_centers[index]
weights = gaussian(x,mean=bin_center,sigma=0.1)
bin_avg[index] = np.average(y,weights=weights)
return (bin_centers,bin_avg)
It is needless to say that this is not working! I am getting the plot with the first value of sigma. Please help...
The following snippet should do more or less what you tried to do. You have mainly a logical problem in your code, x.any() < 100 will always be True, so you'll never execute the second part.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 10, 1000)
y = np.sin(x**2)
def gaussian(x,amp=1,mean=0,sigma=1):
return amp*np.exp(-(x-mean)**2/(2*sigma**2))
def weighted_average(x,y,step_size=0.3):
weights = np.zeros_like(x)
bin_centers = np.arange(np.min(x),np.max(x)-.5*step_size,step_size)+.5*step_size
bin_avg = np.zeros_like(bin_centers)
for i, center in enumerate(bin_centers):
# Select the indices that should count to that bin
idx = ((x >= center-.5*step_size) & (x <= center+.5*step_size))
weights = gaussian(x[idx], mean=center, sigma=step_size)
bin_avg[i] = np.average(y[idx], weights=weights)
return (bin_centers,bin_avg)
idx = x <= 4
plt.plot(*weighted_average(x[idx],y[idx], step_size=0.6))
idx = x >= 3
plt.plot(*weighted_average(x[idx],y[idx], step_size=0.1))
plt.plot(x,y)
plt.legend(['0.6', '0.1', 'y'])
plt.show()
However, depending on the usage, you could also implement moving average directly:
x = np.linspace(0, 60, 1000)
y = np.sin(x**2)
z = np.zeros_like(x)
z[0] = x[0]
for i, t in enumerate(x[1:]):
a=.2
z[i+1] = a*y[i+1] + (1-a)*z[i]
plt.plot(x,y)
plt.plot(x,z)
plt.legend(['data', 'moving average'])
plt.show()
Of course you could then change a adaptively, e.g. depending of the local variance. Also note that this has apriori a small bias depending on a and the step size in x.

Rotate a sphere from coord1 to coord2, where will coord3 be?

I have three coordinates (lat,lon) on a sphere. If you would rotate the whole sphere from coord1 to coord2, where will coord3 now be located?
I've been trying this out in Python using Great Circle (http://www.koders.com/python/fid0A930D7924AE856342437CA1F5A9A3EC0CAEACE2.aspx?s=coastline) but I create strange results as the newly calculated points all group together at the equator. That must have something to do with the azimuth calculation I assume?
Does anyone maybe know how to calculate this correctly?
Thanks in advance!
EDIT
I found the following: http://www.uwgb.edu/dutchs/mathalgo/sphere0.htm
I guess I now need to calculate the rotation axis and the rotation angle from the two points in cartesian coords (and 0,0,0)? I guess this must be very simple, something to do with defining a plane and determining the normal line? Does someone maybe know where I can find the needed equations?
EDIT 2
Coord1 and coord2 make a great circle. Is there an easy way to find the location of the great circle normal axis on the sphere?
EDIT 3
Looks like I was able to solve it ;)
http://articles.adsabs.harvard.edu//full/1953Metic...1...39L/0000039.000.html did the trick.
Ok I don't know the exactly formula, I believe it would be a simple matrix multiplication but here's how you can figure out without it.
Transform coordinates so that the poles of the rotation are at 90,0 and -90,0 respectively and so that the line along your rotation from coord1 to coord2 is then on the "equator" (this should be simply delta lat, delta long)
then the rotation is just change in longitude and you can apply the same delta long to any coord3, then simply transform back to the original coordinates (via negative delta lat and negative delta long)
1 & 2 are pretty much what your matrix would do - if you can figure out the matrices for each step, you can just multiply them and get the final matrix
Using Visual Python I think I now have solved it:
# Rotation first described for geo purposes: http://www.uwgb.edu/dutchs/mathalgo/sphere0.htm
# http://stackoverflow.com/questions/6802577/python-rotation-of-3d-vector
# http://vpython.org/
from visual import *
from math import *
import sys
def ll2cart(lon,lat):
# http://rbrundritt.wordpress.com/2008/10/14/conversion-between-spherical-and-cartesian-coordinates-systems/
x = cos(lat) * cos(lon)
y = cos(lat) * sin(lon)
z = sin(lat)
return x,y,z
def cart2ll(x,y,z):
# http://rbrundritt.wordpress.com/2008/10/14/conversion-between-spherical-and-cartesian-coordinates-systems/
r = sqrt((x**2) + (y**2) + (z**2))
lat = asin(z/r)
lon = atan2(y, x)
return lon, lat
def distance(lon1, lat1, lon2, lat2):
# http://code.activestate.com/recipes/576779-calculating-distance-between-two-geographic-points/
# http://en.wikipedia.org/wiki/Haversine_formula
dlat = lat2 - lat1
dlon = lon2 - lon1
q = sin(dlat/2)**2 + (cos(lat1) * cos(lat2) * (sin(dlon/2)**2))
return 2 * atan2(sqrt(q), sqrt(1-q))
if len(sys.argv) == 1:
sys.exit()
else:
csv = sys.argv[1]
# Points A and B defining the rotation:
LonA = radians(float(sys.argv[2]))
LatA = radians(float(sys.argv[3]))
LonB = radians(float(sys.argv[4]))
LatB = radians(float(sys.argv[5]))
# A and B are both vectors
# The crossproduct AxB is the rotation pole vector P:
Ax, Ay, Az = ll2cart(LonA, LatA)
Bx, By, Bz = ll2cart(LonB, LatB)
A = vector(Ax,Ay,Az)
B = vector(Bx,By,Bz)
P = cross(A,B)
Px,Py,Pz = P
LonP, LatP = cart2ll(Px,Py,Pz)
# The Rotation Angle in radians:
# http://code.activestate.com/recipes/576779-calculating-distance-between-two-geographic-points/
# http://en.wikipedia.org/wiki/Haversine_formula
RotAngle = distance(LonA,LatA,LonB,LatB)
f = open(csv,"r")
o = open(csv[:-4] + "_translated.csv","w")
o.write(f.readline())
for line in f:
(lon, lat) = line.strip().split(",")
# Point C which will be translated:
LonC = radians(float(lon))
LatC = radians(float(lat))
# Point C in Cartesian coordinates:
Cx,Cy,Cz = ll2cart(LonC,LatC)
C = vector(Cx,Cy,Cz)
# C rotated to D:
D = rotate(C,RotAngle,P)
Dx,Dy,Dz = D
LonD,LatD = cart2ll(Dx,Dy,Dz)
o.write(str(degrees(LonD)) + "," + str(degrees(LatD)) + "\n")

Resources