Find favorable amsat pass between two locations.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
168 KiB

  1. {
  2. "cells": [
  3. {
  4. "cell_type": "code",
  5. "execution_count": 1,
  6. "metadata": {},
  7. "outputs": [],
  8. "source": [
  9. "%matplotlib inline\n",
  10. "\n",
  11. "from collections import namedtuple\n",
  12. "from math import degrees\n",
  13. "\n",
  14. "import cartopy.crs as ccrs\n",
  15. "import cartopy.feature as cfeature\n",
  16. "import ephem\n",
  17. "import matplotlib.pyplot as plt\n",
  18. "\n",
  19. "\n",
  20. "philly = ephem.Observer()\n",
  21. "philly.lon, philly.lat = '-75.16961', '39.92435'\n",
  22. "\n",
  23. "portland = ephem.Observer()\n",
  24. "portland.lon, portland.lat = '-122.68092', '45.50901'\n",
  25. "\n",
  26. "fo_29 = ephem.readtle(\n",
  27. " \"JAS-2 (FO-29)\",\n",
  28. " \"1 24278U 96046B 19132.42991715 .00000011 00000-0 48833-4 0 9994\",\n",
  29. " \"2 24278 98.5319 290.9039 0349934 336.8182 21.7496 13.53090691122769\",\n",
  30. ")\n",
  31. "\n",
  32. "lakes = cfeature.NaturalEarthFeature('physical', 'lakes', '50m', facecolor=\"none\")\n",
  33. "countries = cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', '50m', facecolor=\"none\")\n",
  34. "states = cfeature.NaturalEarthFeature('cultural', 'admin_1_states_provinces_lines', '50m', facecolor=\"none\")"
  35. ]
  36. },
  37. {
  38. "cell_type": "code",
  39. "execution_count": 2,
  40. "metadata": {},
  41. "outputs": [],
  42. "source": [
  43. "philly.date = '2019/5/12'\n",
  44. "portland.date = '2019/5/12'\n",
  45. "\n",
  46. "def next_n_passses(location, sat, n):\n",
  47. " passes = []\n",
  48. " for i in range(n):\n",
  49. " info = location.next_pass(sat)\n",
  50. " passes.append((info[0], info[4], info[3]))\n",
  51. " location.date = info[4] + 0.01\n",
  52. " return passes\n",
  53. "\n",
  54. "philly_passes = next_n_passses(philly, fo_29, 20)\n",
  55. "portland_passes = next_n_passses(portland, fo_29, 20)\n",
  56. "\n",
  57. "best_overlap = {\"score\": 10000}\n",
  58. "for o_rise, o_set, o_max in philly_passes:\n",
  59. " for t_rise, t_set, t_max in portland_passes:\n",
  60. " if t_rise < o_set and t_set > o_rise:\n",
  61. " score = abs(o_max - t_max)\n",
  62. " if best_overlap[\"score\"] > score:\n",
  63. " best_overlap = {\n",
  64. " \"score\": score,\n",
  65. " \"rise\": o_rise\n",
  66. " }"
  67. ]
  68. },
  69. {
  70. "cell_type": "code",
  71. "execution_count": 3,
  72. "metadata": {},
  73. "outputs": [],
  74. "source": [
  75. "Point = namedtuple('Point', 'lat lon t')\n",
  76. "ground_track = Point([], [], 0)\n",
  77. "for i in range(300):\n",
  78. " t = best_overlap['rise'] - 0.01 + i/10000.0\n",
  79. " fo_29.compute(t)\n",
  80. " ground_track.lat.append(degrees(fo_29.sublat))\n",
  81. " ground_track.lon.append(degrees(fo_29.sublong))\n",
  82. "\n",
  83. "def dt(t):\n",
  84. " return t.datetime().strftime(\" %H:%M:%S\")\n",
  85. "\n",
  86. "begin = best_overlap['rise'] - 0.01\n",
  87. "philly.date = begin\n",
  88. "philly_pass = philly.next_pass(fo_29)\n",
  89. "fo_29.compute(philly_pass[0])\n",
  90. "philly_aos = Point(degrees(fo_29.sublat), degrees(fo_29.sublong), dt(philly_pass[0]))\n",
  91. "\n",
  92. "fo_29.compute(philly_pass[2])\n",
  93. "philly_max = Point(degrees(fo_29.sublat), degrees(fo_29.sublong), dt(philly_pass[2]))\n",
  94. "\n",
  95. "fo_29.compute(philly_pass[4])\n",
  96. "philly_los = Point(degrees(fo_29.sublat), degrees(fo_29.sublong), dt(philly_pass[4]))\n",
  97. "\n",
  98. "\n",
  99. "portland.date = begin\n",
  100. "portland_pass = portland.next_pass(fo_29)\n",
  101. "fo_29.compute(portland_pass[0])\n",
  102. "portland_aos = Point(degrees(fo_29.sublat), degrees(fo_29.sublong), dt(portland_pass[0]))\n",
  103. "\n",
  104. "fo_29.compute(portland_pass[2])\n",
  105. "portland_max = Point(degrees(fo_29.sublat), degrees(fo_29.sublong), dt(portland_pass[2]))\n",
  106. "\n",
  107. "fo_29.compute(portland_pass[4])\n",
  108. "portland_los = Point(degrees(fo_29.sublat), degrees(fo_29.sublong), dt(portland_pass[4]))"
  109. ]
  110. },
  111. {
  112. "cell_type": "code",
  113. "execution_count": 4,
  114. "metadata": {},
  115. "outputs": [
  116. {
  117. "data": {
  118. "image/png": "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
  119. "text/plain": [
  120. "<Figure size 792x612 with 1 Axes>"
  121. ]
  122. },
  123. "metadata": {
  124. "needs_background": "light"
  125. },
  126. "output_type": "display_data"
  127. }
  128. ],
  129. "source": [
  130. "proj = ccrs.NearsidePerspective(\n",
  131. " central_longitude=-95,\n",
  132. " central_latitude=35,\n",
  133. ")\n",
  134. "geodetic = ccrs.Geodetic()\n",
  135. "\n",
  136. "fig = plt.figure(figsize=(11, 8.5))\n",
  137. "ax = fig.add_axes([0.1, 0.1, 0.9, 0.9], projection=proj)\n",
  138. "ax.set_extent([-140, -50, 5, 85], ccrs.Geodetic())\n",
  139. "\n",
  140. "plt.title(\"FO-29 Pass {d}\".format(d=best_overlap['rise'].datetime().strftime(\"%b %d %Y %H:%M UTC\")), loc='left')\n",
  141. "ax.coastlines(resolution='50m', color=\"grey\", linewidth=0.4, alpha=0.3)\n",
  142. "ax.add_feature(lakes, edgecolor=\"grey\", linewidth=0.4, alpha=0.3)\n",
  143. "ax.add_feature(states, edgecolor=\"grey\", linewidth=0.4, alpha=0.2, dashes='--')\n",
  144. "ax.add_feature(countries, edgecolor=\"grey\", linewidth=0.4, alpha=0.3, dashes='--')\n",
  145. "\n",
  146. "\n",
  147. "ax.plot(ground_track.lon, ground_track.lat, 'k', lw=1, alpha=1, transform=geodetic)\n",
  148. "ax.plot([degrees(philly.lon), philly_aos.lon], [degrees(philly.lat), philly_aos.lat], 'k--', lw=0.5, transform=geodetic)\n",
  149. "ax.plot([degrees(philly.lon), philly_max.lon], [degrees(philly.lat), philly_max.lat], transform=geodetic)\n",
  150. "ax.plot([degrees(philly.lon), philly_los.lon], [degrees(philly.lat), philly_los.lat], 'k--', lw=0.5, transform=geodetic)\n",
  151. "\n",
  152. "ax.plot([degrees(portland.lon), portland_aos.lon], [degrees(portland.lat), portland_aos.lat], 'k--', lw=0.5, transform=geodetic)\n",
  153. "ax.plot([degrees(portland.lon), portland_max.lon], [degrees(portland.lat), portland_max.lat], transform=geodetic)\n",
  154. "ax.plot([degrees(portland.lon), portland_los.lon], [degrees(portland.lat), portland_los.lat], 'k--', lw=0.5, transform=geodetic)\n",
  155. "\n",
  156. "geodetic_transform = ccrs.Geodetic()._as_mpl_transform(ax)\n",
  157. "ax.text(philly_aos.lon, philly_aos.lat, philly_aos.t + \" Philly AOS\", fontsize=8, horizontalalignment='left', transform=geodetic_transform)\n",
  158. "ax.text(philly_max.lon, philly_max.lat, philly_max.t + \" Philly Max Alt\", fontsize=8, horizontalalignment='left', transform=geodetic_transform)\n",
  159. "ax.text(philly_los.lon, philly_los.lat, philly_los.t + \" Philly LOS\", fontsize=8, horizontalalignment='left', transform=geodetic_transform)\n",
  160. "\n",
  161. "ax.text(portland_aos.lon, portland_aos.lat, \"Portland AOS\" + portland_aos.t, fontsize=8, horizontalalignment='right', transform=geodetic_transform)\n",
  162. "ax.text(portland_max.lon, portland_max.lat, \"Portland Max Alt\" + portland_max.t, fontsize=8, horizontalalignment='right', transform=geodetic_transform)\n",
  163. "ax.text(portland_los.lon, portland_los.lat, \"Portland LOS\" + portland_los.t, fontsize=8, horizontalalignment='right', transform=geodetic_transform)\n",
  164. "\n",
  165. "\n",
  166. "plt.savefig(\"map.png\", dpi=190)\n",
  167. "plt.show()"
  168. ]
  169. },
  170. {
  171. "cell_type": "markdown",
  172. "metadata": {},
  173. "source": []
  174. }
  175. ],
  176. "metadata": {
  177. "kernelspec": {
  178. "display_name": "Python 3",
  179. "language": "python",
  180. "name": "python3"
  181. },
  182. "language_info": {
  183. "codemirror_mode": {
  184. "name": "ipython",
  185. "version": 3
  186. },
  187. "file_extension": ".py",
  188. "mimetype": "text/x-python",
  189. "name": "python",
  190. "nbconvert_exporter": "python",
  191. "pygments_lexer": "ipython3",
  192. "version": "3.6.8"
  193. }
  194. },
  195. "nbformat": 4,
  196. "nbformat_minor": 2
  197. }