{"id":11,"date":"2022-04-15T02:02:54","date_gmt":"2022-04-15T02:02:54","guid":{"rendered":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/?p=11"},"modified":"2022-04-15T02:02:54","modified_gmt":"2022-04-15T02:02:54","slug":"week-3-skyfield","status":"publish","type":"post","link":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/2022\/04\/15\/week-3-skyfield\/","title":{"rendered":"Week 3 &#8211; Skyfield"},"content":{"rendered":"\n<p>This week I&#8217;ll focus on the quick dive I&#8217;ve done into the Skyfield Python library and a simple visual test I did to see if results for tracking the ISS matched the various satellite tracking webpages. <\/p>\n\n\n\n<p>The Skyfield website has a great guide on how to track <a href=\"https:\/\/rhodesmill.org\/skyfield\/earth-satellites.html\">earth satellites<\/a> in a couple different ways which is where these code snippets came from. First, you create a satellite object from TLE data, then use the various Skyfield functions to propagate its position, get its rise and set times, etc. <\/p>\n\n\n\n<p>The first step was to get the TLE data for the ISS which Skyfield makes very simple with its <code>load<\/code> module. This module makes the API call to CelesTrak and returns a list of matching satellites. <\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code>n = 25544\nurl = 'https:\/\/celestrak.com\/satcat\/tle.php?CATNR={}'.format(n)\nfilename = 'tle-CATNR-{}.txt'.format(n)\nsatellites = load.tle_file(url, filename=filename)\nsatellite = satellites&#091;0]<\/code><\/pre>\n\n\n\n<p>25544 is the NORAD ID for the ISS, but it can also be searched for by name.<\/p>\n\n\n\n<p>The next step is to create a date object and set it to now, since we want to propagate satellite positions starting from the current time.<\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code>ts = load.timescale()\nt = ts.now()<\/code><\/pre>\n\n\n\n<p>With the satellite and date objects, we can generate ephemeris for the ISS. Skyfield makes this exceedingly simple.<\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code>geocentric = satellite.at(t)<\/code><\/pre>\n\n\n\n<p>The <code>geocentric<\/code> variable now contains the x,y,z position of the satellite relative to the Earth&#8217;s center in the Geocentric Celestial Reference System (GCRS) at the time contained in <code>t<\/code>. Right now at 6:32pm PST on April 14, 2022 the ISS is at about <code>[-4238.11808253 -592.82098901 -5286.05509265]<\/code> in the GCRS system.<\/p>\n\n\n\n<p>For the purposes of plotting these positions on a map, Skyfield has a module called <code>wgs84<\/code> (World Geodetic System) that will convert GCRS to latitude, longitude, and height. <\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code>lat, lon = wgs84.latlon_of(geocentric)\nheight = wgs84.height_of(geocentric)<\/code><\/pre>\n\n\n\n<p>With this system for generating a position for a specific time, we can simply repeat the <code>satellite.at(t)<\/code> call with an incremented time variable.<\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code>t += timedelta(minutes=1)<\/code><\/pre>\n\n\n\n<p>The plotted Skyfield points seem pretty accurate when compared to <a href=\"https:\/\/www.n2yo.com\/?s=25544\">n2yo.com<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/5530\/files\/2022\/04\/Screenshot-2022-04-14-185609-1024x576.png\" alt=\"\" class=\"wp-image-26\" srcset=\"https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/5530\/files\/2022\/04\/Screenshot-2022-04-14-185609-1024x576.png 1024w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/5530\/files\/2022\/04\/Screenshot-2022-04-14-185609-300x169.png 300w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/5530\/files\/2022\/04\/Screenshot-2022-04-14-185609-768x432.png 768w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/5530\/files\/2022\/04\/Screenshot-2022-04-14-185609-1536x864.png 1536w, https:\/\/osu-wams-blogs-uploads.s3.amazonaws.com\/blogs.dir\/5530\/files\/2022\/04\/Screenshot-2022-04-14-185609.png 1921w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The second Skyfield function I looked into was <code>find_events<\/code>. This function is called on a satellite object using an observer&#8217;s position, time frame (start\/end), and an altitude. It will then calculate when the satellite with rise above the altitude, culminate, and set below the altitude. This example is taken directly from the <a href=\"https:\/\/rhodesmill.org\/skyfield\/earth-satellites.html#finding-when-a-satellite-rises-and-sets\">Skyfield website<\/a>. <\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code># Find how many times the ISS rises over 30 degrees of altitude over a day\nbluffton = wgs84.latlon(+40.8939, -83.8917)\nt0 = ts.utc(2022, 4, 14)\nt1 = ts.utc(2022, 4, 15)\nt, events = satellite.find_events(bluffton, t0, t1, altitude_degrees=30.0)\nfor ti, event in zip(t, events):\n    name = ('rise above 30\u00b0', 'culminate', 'set below 30\u00b0')&#091;event]\n    print(ti.utc_strftime('%Y %b %d %H:%M:%S'), name)<\/code><\/pre>\n\n\n\n<p>Results for events on April 14, 2022:<\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code>&#091;&lt;EarthSatellite ISS (ZARYA) catalog #25544 epoch 2022-04-14 17:38:40 UTC&gt;]\n2022 Apr 14 14:47:40 rise above 30\u00b0\n2022 Apr 14 14:48:59 culminate\n2022 Apr 14 14:50:18 set below 30\u00b0\n2022 Apr 14 19:40:53 rise above 30\u00b0\n2022 Apr 14 19:41:36 culminate\n2022 Apr 14 19:42:19 set below 30\u00b0\n2022 Apr 14 21:17:55 rise above 30\u00b0\n2022 Apr 14 21:18:18 culminate\n2022 Apr 14 21:18:42 set below 30\u00b0<\/code><\/pre>\n\n\n\n<p><strong>Full scripts<\/strong><\/p>\n\n\n\n<p>Here is my full script for plotting one hour of future ISS positions on a map using Folium:<\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code>from datetime import timedelta\nfrom skyfield.api import load, wgs84\nimport folium\n\n# Create ISS TLE\nn = 25544\nurl = 'https:\/\/celestrak.com\/satcat\/tle.php?CATNR={}'.format(n)\nfilename = 'tle-CATNR-{}.txt'.format(n)\nsatellites = load.tle_file(url, filename=filename)\nprint(satellites)\n\nsatellite = satellites&#091;0]\n\nts = load.timescale()\nt = ts.now()\n\n# Create Folium map\noutput_file = 'map.html'\nmap = folium.Map()\n\nfor i in range(60):\n    # Get GCRS coordinates of ISS\n    geocentric = satellite.at(t)\n    print(geocentric.position.km, end=' ')\n\n    # Convert GCRS coordinates to lat, lon, and height\n    lat, lon = wgs84.latlon_of(geocentric)\n    height = wgs84.height_of(geocentric)\n    print('Lat:', lat, end=', ')\n    print('Lon:', lon, end=', ')\n    print('Height:', height.km, end='km\\n')\n\n    if i == 0:\n        folium.Marker(&#091;lat.degrees, lon.degrees]).add_to(map)\n    else:\n        folium.CircleMarker(&#091;lat.degrees, lon.degrees], radius=3, color='red').add_to(map)\n\n    t += timedelta(minutes=1)\n    \nmap.fit_bounds(map.get_bounds())\nmap.save(output_file)<\/code><\/pre>\n\n\n\n<p>Here is my full script for getting rise and set times:<\/p>\n\n\n\n<pre class=\"wp-block-code has-secondary-background-color has-background\"><code>from skyfield.api import load, wgs84\n\n# Create ISS TLE\nn = 25544\nurl = 'https:\/\/celestrak.com\/satcat\/tle.php?CATNR={}'.format(n)\nfilename = 'tle-CATNR-{}.txt'.format(n)\nsatellites = load.tle_file(url, filename=filename)\nprint(satellites)\n\nsatellite = satellites&#091;0]\n\nts = load.timescale()\n\n# Find how many times the ISS rises over 30 degrees of altitude over a day\nbluffton = wgs84.latlon(+40.8939, -83.8917)\nt0 = ts.utc(2022, 4, 14)\nt1 = ts.utc(2022, 4, 15)\nt, events = satellite.find_events(bluffton, t0, t1, altitude_degrees=30.0)\nfor ti, event in zip(t, events):\n    name = ('rise above 30\u00b0', 'culminate', 'set below 30\u00b0')&#091;event]\n    print(ti.utc_strftime('%Y %b %d %H:%M:%S'), name)<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>This week I&#8217;ll focus on the quick dive I&#8217;ve done into the Skyfield Python library and a simple visual test I did to see if results for tracking the ISS matched the various satellite tracking webpages. The Skyfield website has a great guide on how to track earth satellites in a couple different ways which [&hellip;]<\/p>\n","protected":false},"author":12370,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-11","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/posts\/11","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/users\/12370"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/comments?post=11"}],"version-history":[{"count":15,"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/posts\/11\/revisions"}],"predecessor-version":[{"id":27,"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/posts\/11\/revisions\/27"}],"wp:attachment":[{"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/media?parent=11"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/categories?post=11"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.oregonstate.edu\/spudlaunching\/wp-json\/wp\/v2\/tags?post=11"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}