├── AstroFunctions.py ├── AstroFunctions.pyc ├── Coastline.txt ├── License.txt ├── README.md ├── main_00_iss.py ├── main_00_iss_ground_track.pdf ├── main_01_tiangong.py └── main_01_tiangong_ground_track.pdf /AstroFunctions.py: -------------------------------------------------------------------------------- 1 | """ 2 | AstroFunctions.py 3 | 4 | Astrodynamics Functions 5 | 6 | (Only a few functions from my original AstroFunctions.py are included here. ) 7 | 8 | Description: 9 | Various Python functions useful for astrodynamics applications. 10 | Most of these functions are based on Fundamentals of Astrodynamics, Vallado. 4th ed. 11 | 12 | Author: Ashiv Dhondea, RRSG, UCT. 13 | Date: 05 December 2016 14 | """ 15 | # ------------------------------------------------------------------------------------------ # 16 | import numpy as np 17 | import math 18 | # ------------------------------------------------------------------------------------------ # 19 | def fnSeconds_To_Hours(time_period): 20 | """ 21 | Convert from seconds to hours, minutes and seconds. 22 | 23 | Date: 16 October 2016 24 | """ 25 | num_hrs = int(time_period/(60.*60.)); 26 | time_period =time_period - num_hrs*60.*60.; 27 | num_mins = int(time_period/60.); 28 | num_secs = time_period - num_mins*60.; 29 | return num_hrs,num_mins,num_secs # edit: 1/12/16: float division and multiplication 30 | 31 | def fn_Convert_Datetime_to_GMST(datetime_object): 32 | """ 33 | Converts a date and time in the datetime object form 34 | to GMST. 35 | 36 | Date: 05 October 2016 37 | """ 38 | obj = datetime_object; 39 | julianday = fnJulianDate(obj.year,obj.month,obj.day,obj.hour,obj.minute,obj.second); 40 | theta_GMST = fn_Calculate_GMST(julianday); 41 | return theta_GMST # validated with example 3-5 in vallado. 42 | 43 | def fnZeroTo2Pi(rotangle): 44 | """ 45 | Wraps angle to fit in [0,2pi). 46 | Works in [rad] not [deg] 47 | Date: 7 October 2016 48 | """ 49 | wrappedangle = rotangle % (2*math.pi); 50 | return wrappedangle 51 | 52 | def fnECItoECEF(ECI,theta): 53 | ECEF = np.zeros([3],dtype=np.float64); 54 | # Rotating the ECI vector into the ECEF frame via the GST angle about the Z-axis 55 | ECEF = np.dot(fnRotate3(theta),ECI); 56 | return ECEF 57 | 58 | def fnJulianDate(yr, mo, d, h, m, s): 59 | """ 60 | Implements Algo 14 in Vallado book: JulianDate 61 | Date: 05 October 2016 62 | """ 63 | JD = 367.0*yr - int((7*(yr+ int((mo+9)/12)))/4.0) + int((275.0*mo)/9.0) + d+ 1721013.5 + ((((s/60.0)+m)/60+h)/24.0); 64 | return JD # validated with example 3-4 in vallado. 65 | 66 | def fn_Calculate_GMST(JD): 67 | """ 68 | Calculates the Greenwich Mean Sidereal Time according to eqn 3-47 on page 188 in Vallado. 69 | Date: 05 October 2016 70 | Edit: 06 October 2016: CAUTION: theta_GMST is output in [degrees] rather than in [radians], 71 | unlike most of the angles in this file. 72 | """ 73 | T_UT1 = (JD - 2451545.0)/36525.0 74 | theta_GMST = 67310.54841 + (876600.0*60*60 + 8640184.812866)*T_UT1 + 0.093104 * T_UT1**2 - 6.2e-6 * T_UT1**3; 75 | 76 | while theta_GMST > 86400.0: 77 | theta_GMST = theta_GMST - 86400; 78 | 79 | theta_GMST = theta_GMST/240.0; 80 | theta_GMST = theta_GMST - 360; # in [deg] not [rad] !!!!!!!!!!!!!!!!!!!!!!!!!!! 81 | return theta_GMST # validated with example 3-5 in vallado. 82 | 83 | def fnRotate3(alpha_rad): 84 | T = np.array([[ math.cos(alpha_rad),math.sin(alpha_rad),0], 85 | [-math.sin(alpha_rad),math.cos(alpha_rad),0], 86 | [ 0, 0,1]],dtype=np.float64); 87 | return T # Validated against Vallado's example 2-3. 20/06/16 88 | 89 | def fnCarts_to_LatLon(R): 90 | """ 91 | function which converts ECEF position vectors to latitude and longitude 92 | Based on rvtolatlong.m in Richard Rieber's orbital library on mathwork.com 93 | 94 | Note that this is only suitable for groundtrack visualization, not rigorous 95 | calculations. 96 | Date: 18 September 2016 97 | """ 98 | r_delta = np.linalg.norm(R[0:1]); 99 | sinA = R[1]/r_delta; 100 | cosA = R[0]/r_delta; 101 | 102 | Lon = math.atan2(sinA,cosA); 103 | 104 | if Lon < -math.pi: 105 | Lon = Lon + 2*math.pi; 106 | 107 | Lat = math.asin(R[2]/np.linalg.norm(R)); 108 | return Lat,Lon 109 | -------------------------------------------------------------------------------- /AstroFunctions.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshivDhondea/Satellite_Ground_Track_Plotting_Python/957d39a6e0f138d19d29f6f47105b6f8093ea1ca/AstroFunctions.pyc -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ashiv Dhondea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Satellite_Ground_Track_Plotting_Python 2 | Python codes to plot satellite ground tracks. 3 | 4 | Orbital mechanics and orbit visualization codes which I have developed in 2015-2016. 5 | Coastline map data from https://www.mathworks.com/matlabcentral/fileexchange/13439-orbital-mechanics-library/content/Groundtrack.m 6 | -------------------------------------------------------------------------------- /main_00_iss.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | main_00_iss.py 4 | 5 | Satellite ground track plotting. 6 | 7 | Demonstration of satellite ground track plotting. 8 | 9 | Reads in the position vectors in the Earth Centered Inertial or the Earth Centered Earth Fixed Frame, 10 | converts these to latitude and longitude and plots the location on an equirectangular-projected map. 11 | 12 | Author: Ashiv Dhondea, RRSG, UCT. 13 | Date: 05 December 2016 14 | """ 15 | import math 16 | import numpy as np 17 | import matplotlib.pyplot as plt 18 | from matplotlib.lines import Line2D 19 | from mpl_toolkits.axes_grid.anchored_artists import AnchoredText 20 | from matplotlib import colors 21 | import matplotlib.patches as patches 22 | import matplotlib as mpl 23 | 24 | import datetime as dt 25 | import pytz 26 | import aniso8601 27 | from sgp4.earth_gravity import wgs72 28 | from sgp4.io import twoline2rv 29 | 30 | import AstroFunctions as AstFn 31 | 32 | # ------------------------------------------------------------------------------------------------ # 33 | ## ISS (ZARYA) 34 | tle_line1 = '1 25544U 98067A 16298.89519381 .00003992 00000-0 67065-4 0 9991'; 35 | tle_line2 = '2 25544 51.6430 131.0922 0007174 106.9148 32.6949 15.54320225 25163'; 36 | line1 = (tle_line1); 37 | line2 = (tle_line2); 38 | 39 | satellite_obj = twoline2rv(line1, line2, wgs72); 40 | print 'satellite number' 41 | print satellite_obj.satnum 42 | print 'epochyr' 43 | print satellite_obj.epochyr 44 | print 'epochdays' 45 | print satellite_obj.epochdays 46 | print 'jdsatepoch' 47 | print satellite_obj.jdsatepoch 48 | print 'epoch' 49 | print satellite_obj.epoch 50 | print 'inclination' 51 | print math.degrees(satellite_obj.inclo) 52 | print 'RAAN' 53 | print math.degrees(satellite_obj.nodeo) 54 | print 'eccentricity' 55 | print satellite_obj.ecco 56 | print 'argument of perigee' 57 | print math.degrees(satellite_obj.argpo) 58 | print 'mean anomaly' 59 | print math.degrees(satellite_obj.mo) 60 | 61 | delta_t = 1; #[s] 62 | simulation_period = 95*60*2 ;#[s] 63 | timevec = np.arange(0,simulation_period+delta_t,delta_t,dtype=np.float64); 64 | x_state = np.zeros([6,len(timevec)],dtype=np.float64); 65 | xecef = np.zeros([3,len(timevec)],dtype=np.float64); 66 | lat = np.zeros([len(timevec)],dtype=np.float64); 67 | lon = np.zeros([len(timevec)],dtype=np.float64); 68 | 69 | index = 0; 70 | current_time = timevec[index]; 71 | hrs,mins,secs = AstFn.fnSeconds_To_Hours(current_time + (satellite_obj.epoch.hour*60*60) + (satellite_obj.epoch.minute*60)+ satellite_obj.epoch.second); 72 | dys = satellite_obj.epoch.day + int(math.ceil(hrs/24)); 73 | if hrs >= 24: 74 | hrs = hrs - 24*int(math.ceil(hrs/24)) ; 75 | 76 | satpos,satvel = satellite_obj.propagate(satellite_obj.epoch.year,satellite_obj.epoch.month,dys,hrs,mins,secs+(1e-6)*satellite_obj.epoch.microsecond); 77 | x_state[0:3,index] = np.asarray(satpos); 78 | x_state[3:6,index] = np.asarray(satvel); 79 | 80 | tle_epoch_test = dt.datetime(year=satellite_obj.epoch.year,month=satellite_obj.epoch.month,day=int(dys),hour=int(hrs),minute=int(mins),second=int(secs),microsecond=0,tzinfo= pytz.utc); 81 | theta_GMST = math.radians(AstFn.fn_Convert_Datetime_to_GMST(tle_epoch_test)); 82 | ## Rotate ECI position vector by GMST angle to get ECEF position 83 | theta_GMST = AstFn.fnZeroTo2Pi(theta_GMST); 84 | xecef[:,index] = AstFn.fnECItoECEF(x_state[0:3,index],theta_GMST); 85 | lat[index],lon[index] = AstFn.fnCarts_to_LatLon(xecef[:,index]); 86 | 87 | for index in range(1,len(timevec)): 88 | current_time = timevec[index]; 89 | hrs,mins,secs = AstFn.fnSeconds_To_Hours(current_time + (satellite_obj.epoch.hour*60*60) + (satellite_obj.epoch.minute*60)+ satellite_obj.epoch.second); 90 | dys = satellite_obj.epoch.day + int(math.ceil(hrs/24)); 91 | 92 | if hrs >= 24: 93 | hrs = hrs - 24*int(math.ceil(hrs/24)) ; 94 | 95 | satpos,satvel = satellite_obj.propagate(satellite_obj.epoch.year,satellite_obj.epoch.month,dys,hrs,mins,secs+(1e-6)*satellite_obj.epoch.microsecond); 96 | x_state[0:3,index] = np.asarray(satpos); 97 | x_state[3:6,index] = np.asarray(satvel); 98 | 99 | tle_epoch_test = dt.datetime(year=satellite_obj.epoch.year,month=satellite_obj.epoch.month,day=int(dys),hour=int(hrs),minute=int(mins),second=int(secs),microsecond=0,tzinfo= pytz.utc); 100 | theta_GMST = math.radians(AstFn.fn_Convert_Datetime_to_GMST(tle_epoch_test)); 101 | ## Rotate ECI position vector by GMST angle to get ECEF position 102 | theta_GMST = AstFn.fnZeroTo2Pi(theta_GMST); 103 | xecef[:,index] = AstFn.fnECItoECEF(x_state[0:3,index],theta_GMST); 104 | lat[index],lon[index] = AstFn.fnCarts_to_LatLon(xecef[:,index]); 105 | 106 | # ------------------------------------------------------------------------------------------------------------------------------------------------------------ # 107 | ## plot results 108 | coastline_data= np.loadtxt('Coastline.txt',skiprows=1) 109 | w, h = plt.figaspect(0.5) 110 | fig = plt.figure(figsize=(w,h)) 111 | ax = fig.gca() 112 | plt.rc('text', usetex=True) 113 | plt.rc('font', family='serif'); 114 | plt.rc('font',family='helvetica'); 115 | params = {'legend.fontsize': 8, 116 | 'legend.handlelength': 2} 117 | plt.rcParams.update(params) 118 | 119 | groundtrack_title = satellite_obj.epoch.strftime('%d %B %Y') 120 | fig.suptitle(r"\textbf{ISS Ground Track on %s}" %groundtrack_title,fontsize=16) 121 | plt.plot(coastline_data[:,0],coastline_data[:,1],'g'); 122 | ax.set_xlabel(r'Longitude $[\mathrm{^\circ}]$',fontsize=14) 123 | ax.set_ylabel(r'Latitude $[\mathrm{^\circ}]$',fontsize=14) 124 | plt.xlim(-180,180); 125 | plt.ylim(-90,90); 126 | plt.yticks([-90,-80,-70,-60,-50,-40,-30,-20,-10,0,10,20,30,40,50,60,70,80,90]); 127 | plt.xticks([-180,-150,-120,-90,-60,-30,0,30,60,90,120,150,180]); 128 | 129 | for index in range(0,len(timevec)): 130 | plt.plot(math.degrees(lon[index]),math.degrees(lat[index]),'b.',markersize=1); 131 | 132 | ax.grid(True); 133 | at = AnchoredText("AshivD",prop=dict(size=5), frameon=True,loc=4) 134 | at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2") 135 | ax.add_artist(at) 136 | fig.savefig('main_00_iss_ground_track.pdf',format='pdf',bbox_inches='tight',pad_inches=0.01,dpi=1200); 137 | 138 | 139 | -------------------------------------------------------------------------------- /main_00_iss_ground_track.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshivDhondea/Satellite_Ground_Track_Plotting_Python/957d39a6e0f138d19d29f6f47105b6f8093ea1ca/main_00_iss_ground_track.pdf -------------------------------------------------------------------------------- /main_01_tiangong.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | main_01_tiangong.py 4 | 5 | Satellite ground track plotting. 6 | 7 | Demonstration of satellite ground track plotting. 8 | 9 | Reads in the position vectors in the Earth Centered Inertial or the Earth Centered Earth Fixed Frame, 10 | converts these to latitude and longitude and plots the location on an equirectangular-projected map. 11 | 12 | Author: Ashiv Dhondea, RRSG, UCT. 13 | Date: 05 December 2016 14 | """ 15 | import math 16 | import numpy as np 17 | import matplotlib.pyplot as plt 18 | from matplotlib.lines import Line2D 19 | from mpl_toolkits.axes_grid.anchored_artists import AnchoredText 20 | from matplotlib import colors 21 | import matplotlib.patches as patches 22 | import matplotlib as mpl 23 | 24 | import datetime as dt 25 | import pytz 26 | import aniso8601 27 | from sgp4.earth_gravity import wgs72 28 | from sgp4.io import twoline2rv 29 | 30 | import AstroFunctions as AstFn 31 | 32 | # ------------------------------------------------------------------------------------------------ # 33 | ## TIANGONG 1 34 | tle_line1 = '1 37820U 11053A 16339.16302693 .00021051 00000-0 17357-3 0 9997'; 35 | tle_line2 = '2 37820 42.7632 305.9790 0015673 147.2225 332.7571 15.69763185297236'; 36 | 37 | line1 = (tle_line1); 38 | line2 = (tle_line2); 39 | 40 | satellite_obj = twoline2rv(line1, line2, wgs72); 41 | print 'satellite number' 42 | print satellite_obj.satnum 43 | print 'epochyr' 44 | print satellite_obj.epochyr 45 | print 'epochdays' 46 | print satellite_obj.epochdays 47 | print 'jdsatepoch' 48 | print satellite_obj.jdsatepoch 49 | print 'epoch' 50 | print satellite_obj.epoch 51 | print 'inclination' 52 | print math.degrees(satellite_obj.inclo) 53 | print 'RAAN' 54 | print math.degrees(satellite_obj.nodeo) 55 | print 'eccentricity' 56 | print satellite_obj.ecco 57 | print 'argument of perigee' 58 | print math.degrees(satellite_obj.argpo) 59 | print 'mean anomaly' 60 | print math.degrees(satellite_obj.mo) 61 | 62 | delta_t = 1; #[s] 63 | simulation_period = 720*60 ;#[s], 720 min of simulation 64 | timevec = np.arange(0,simulation_period+delta_t,delta_t,dtype=np.float64); 65 | x_state = np.zeros([6,len(timevec)],dtype=np.float64); 66 | xecef = np.zeros([3,len(timevec)],dtype=np.float64); 67 | lat = np.zeros([len(timevec)],dtype=np.float64); 68 | lon = np.zeros([len(timevec)],dtype=np.float64); 69 | 70 | index = 0; 71 | current_time = timevec[index]; 72 | hrs,mins,secs = AstFn.fnSeconds_To_Hours(current_time + (satellite_obj.epoch.hour*60*60) + (satellite_obj.epoch.minute*60)+ satellite_obj.epoch.second); 73 | dys = satellite_obj.epoch.day + int(math.ceil(hrs/24)); 74 | if hrs >= 24: 75 | hrs = hrs - 24*int(math.ceil(hrs/24)) ; 76 | 77 | satpos,satvel = satellite_obj.propagate(satellite_obj.epoch.year,satellite_obj.epoch.month,dys,hrs,mins,secs+(1e-6)*satellite_obj.epoch.microsecond); 78 | x_state[0:3,index] = np.asarray(satpos); 79 | x_state[3:6,index] = np.asarray(satvel); 80 | 81 | tle_epoch_test = dt.datetime(year=satellite_obj.epoch.year,month=satellite_obj.epoch.month,day=int(dys),hour=int(hrs),minute=int(mins),second=int(secs),microsecond=0,tzinfo= pytz.utc); 82 | theta_GMST = math.radians(AstFn.fn_Convert_Datetime_to_GMST(tle_epoch_test)); 83 | ## Rotate ECI position vector by GMST angle to get ECEF position 84 | theta_GMST = AstFn.fnZeroTo2Pi(theta_GMST); 85 | xecef[:,index] = AstFn.fnECItoECEF(x_state[0:3,index],theta_GMST); 86 | lat[index],lon[index] = AstFn.fnCarts_to_LatLon(xecef[:,index]); 87 | 88 | for index in range(1,len(timevec)): 89 | current_time = timevec[index]; 90 | hrs,mins,secs = AstFn.fnSeconds_To_Hours(current_time + (satellite_obj.epoch.hour*60*60) + (satellite_obj.epoch.minute*60)+ satellite_obj.epoch.second); 91 | dys = satellite_obj.epoch.day + int(math.ceil(hrs/24)); 92 | 93 | if hrs >= 24: 94 | hrs = hrs - 24*int(math.ceil(hrs/24)) ; 95 | 96 | satpos,satvel = satellite_obj.propagate(satellite_obj.epoch.year,satellite_obj.epoch.month,dys,hrs,mins,secs+(1e-6)*satellite_obj.epoch.microsecond); 97 | x_state[0:3,index] = np.asarray(satpos); 98 | x_state[3:6,index] = np.asarray(satvel); 99 | 100 | tle_epoch_test = dt.datetime(year=satellite_obj.epoch.year,month=satellite_obj.epoch.month,day=int(dys),hour=int(hrs),minute=int(mins),second=int(secs),microsecond=0,tzinfo= pytz.utc); 101 | theta_GMST = math.radians(AstFn.fn_Convert_Datetime_to_GMST(tle_epoch_test)); 102 | ## Rotate ECI position vector by GMST angle to get ECEF position 103 | theta_GMST = AstFn.fnZeroTo2Pi(theta_GMST); 104 | xecef[:,index] = AstFn.fnECItoECEF(x_state[0:3,index],theta_GMST); 105 | lat[index],lon[index] = AstFn.fnCarts_to_LatLon(xecef[:,index]); 106 | 107 | # ------------------------------------------------------------------------------------------------------------------------------------------------------------ # 108 | ## plot results 109 | coastline_data= np.loadtxt('Coastline.txt',skiprows=1) 110 | # Coastline map data obtained from 111 | # https://www.mathworks.com/matlabcentral/fileexchange/13439-orbital-mechanics-library/content/Groundtrack.m 112 | 113 | w, h = plt.figaspect(0.5) 114 | fig = plt.figure(figsize=(w,h)) 115 | ax = fig.gca() 116 | plt.rc('text', usetex=True) 117 | plt.rc('font', family='serif'); 118 | plt.rc('font',family='helvetica'); 119 | params = {'legend.fontsize': 8, 120 | 'legend.handlelength': 2} 121 | plt.rcParams.update(params) 122 | 123 | groundtrack_title = satellite_obj.epoch.strftime('%d %B %Y') 124 | fig.suptitle(r"\textbf{Tiangong-1 Ground Track on %s}" %groundtrack_title,fontsize=16) 125 | plt.plot(coastline_data[:,0],coastline_data[:,1],'g'); 126 | ax.set_xlabel(r'Longitude $[\mathrm{^\circ}]$',fontsize=14) 127 | ax.set_ylabel(r'Latitude $[\mathrm{^\circ}]$',fontsize=14) 128 | plt.xlim(-180,180); 129 | plt.ylim(-90,90); 130 | plt.yticks([-90,-80,-70,-60,-50,-40,-30,-20,-10,0,10,20,30,40,50,60,70,80,90]); 131 | plt.xticks([-180,-150,-120,-90,-60,-30,0,30,60,90,120,150,180]); 132 | 133 | for index in range(0,len(timevec)): 134 | plt.plot(math.degrees(lon[index]),math.degrees(lat[index]),'b.',markersize=1); 135 | 136 | ax.grid(True); 137 | at = AnchoredText("AshivD",prop=dict(size=5), frameon=True,loc=4) 138 | at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2") 139 | ax.add_artist(at) 140 | fig.savefig('main_01_tiangong_ground_track.pdf',format='pdf',bbox_inches='tight',pad_inches=0.01,dpi=1200); 141 | 142 | -------------------------------------------------------------------------------- /main_01_tiangong_ground_track.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AshivDhondea/Satellite_Ground_Track_Plotting_Python/957d39a6e0f138d19d29f6f47105b6f8093ea1ca/main_01_tiangong_ground_track.pdf --------------------------------------------------------------------------------