The general equation for the change in frequency due to Doppler shift is:
where c is the speed of light, fO is the frequency of operation, and Δv is the relative velocity.
When applying this formula to the ground observation of a satellite, Δv is better described as range velocity (rate of change of distance from the satellite to observer). Because many LEO satellites are in an elliptical orbit, the math to obtain Δv as a function of time or position is not solved through simple angular calculations and therefore becomes very tedious to execute even when applying matrix math for some of the calculations.
I would like to suggest, however, that there is little value in recreating all of the calculations except for academic interest. Instead, I prefer to use a well engineered software library called PyEphem that does nearly every type of ephemeral calculation one could need (I even use this library in my home automation system to calculate the on/off times of my exterior lights to obtain automatic seasonal adjustment). This is a completely free Python library that is easily run on nearly any operating system and platform including the ubiquitous Raspberry Pi.
Applying the library for amateur radio satellites is quite simple. First, acquire the TLE (two line elements) for the desired satellite. These are available from the TLE Info site. A typical set of TLE data contains all the necessary ephemeral variables and looks like this:
OSCAR 7
1 7530U 74089B 17170.24378275 -.00000031 +00000-0 +84707-4 0 9992
2 7530 101.6303 138.8875 0011838 320.4872 153.4129 12.53627377948883
This information is used with the PyEmphem library in the following fashion. First establish the observer location on earth (I will use Chicago, IL, USA as an example):
my_loc = ephem.Observer()
my_loc.lon = '87.6298'
my_loc.lat = '41.8781'
my_loc.elevation = 181
Then apply the TLE data to create a my_sat body object:
my_sat = ephem.readtle(name, line1, line2);
The library can now calculate a variety of data related to the next pass of my_sat at my_loc:
info=my_loc.next_pass(my_sat)
So for example, we can print the rise and set times, maximum altitude, and the time of the maximum altitude for the next pass:
print("AOS: %s LOS: %s Maximum Altitude: %s Maximum Altitude Time: %s" % (info[0], info[4], info[3], info[2]))
Note that the maximum Doppler shift will occur at AOS and LOS and that the maximum altitude time is when the Doppler shift will be at zero because the range velocity (Δv) will drop to zero.
We can also inquire as to the range velocity at any time during the pass. For example, at the start of the pass:
my_sat.compute(info[0])
print("Range velocity: %s " % (my_sat.range_velocity))
By applying this technique to obtain the range velocity throughout the pass, we can easily calculate the Δf of a specific pass as viewed from a specific location on earth. And all of that for only a few dozen lines of code!
You can learn more about the expansive PyEphem library here.
If you wish to explore more of the raw math behind LEO satellite calculations, the following links may be helpful:
Orbit Calculation and Doppler Correction
Adaptive Doppler Correction
Satellite Orbit Basics