A Planet Position Widget

speckx1 pts0 comments

A planet position widget - All this

And now it’s all this

I just said what I said and it was wrong

Or was taken wrong

Previous post

A planet position widget

August 12, 2026 at 12:16 PM by Dr. Drang

After I learned how to make a Mac widget with TerminalWidget and how to determine the locations of celestial objects with Astropy, the natural thing for me to do was combine the two into a widget that tracks the planets.

I’m using the ancient definition of planet, which includes the Sun and Moon but not anything past Saturn. The numbers are the azimuth and altitude, in that order, and are given to the nearest degree. The idea is to tell me what may be visible and where it is. This particular screenshot was taken just after 10:00 last night; there was no reason to go outside because everything was below the horizon.

I was torn on whether to include the Sun. Few of us need help finding the Sun in the sky, and you can’t see anything other than the Moon when the Sun is up. But I decided to include it anyway, partly for completeness, and partly because the visibility of some bodies depends on their separation from the Sun.

Let’s start with the code that generates the widget’s text. It’s a Python script called planets:

python:<br>1: import astropy.units as u<br>2: from astropy.time import Time<br>3: from astropy.coordinates import get_body, AltAz, EarthLocation<br>4: from subprocess import run<br>5:<br>6: def direction(az):<br>7: 'Return a string indication of the azimuth (given in degrees).'<br>8:<br>9: dirs = 'N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW'.split()<br>10: i = int(((az + 11.25) % 360) / 22.5)<br>11: return dirs[i]<br>12:<br>13: # Current time in UTC.<br>14: ut = Time.now()<br>15:<br>16: # Observation location.<br>17: home = EarthLocation(lat=41.81433*u.deg, lon=-88.07093*u.deg, height=208*u.m)<br>18:<br>19: # Bodies of interest.<br>20: planets = 'Moon Sun Mercury Venus Mars Jupiter Saturn'.split()<br>21:<br>22: # Current positions of all the bodies.<br>23: pos = {}<br>24: const = {}<br>25: for p in planets:<br>26: pos[p] = get_body(p, ut).transform_to(AltAz(obstime=ut, location=home))<br>27: const[p] = pos[p].get_constellation()<br>28:<br>29: # Assemble the results.<br>30: output = []<br>31: for p in planets:<br>32: output.append(f'{p:>8s}: {pos[p].az.value:3.0f} \<br>33: {direction(pos[p].az.value):3s} {pos[p].alt.value:3.0f} {const[p]}')<br>34:<br>35: # Pipe the results through TerminalWidget.<br>36: tw = '/Applications/TerminalWidget.app/Contents/MacOS/TerminalWidget\<br>37: --target planets --font Menlo --bg eeeeee --fg 000000 --text -'.split()<br>38: run(tw, input='\n'.join(output).encode())<br>39:<br>40: # print('\n'.join(output))

There’s no shebang line because of how it gets called by launchd, which we’ll get to later.

After planets imports the necessary modules, Lines 6–11 define the direction function, which takes the azimuth and returns a string with the corresponding point of the compass. I have this because 223°, which is how Astropy reports the azimuth, doesn’t immediately say "southwest" to me. The function assumes a 16-point compass, like this one:

Image from Wikipedia.

The points are separated by 22.5°, which is why there’s a division by 22.5 in Line 10. The other parts of Line 10 adjust for the fact that North starts at 348.75° (-11.25°), the azimuth resets at 360°, and the index of a list must be an integer. Astropy may already have a function that does what direction does, but I thought it would be easier (and more fun) to write the function myself than to search through the documentation.

Lines 14 and 17 define the time and place of observation. planets will be run every half hour to update the widget, so what’s being displayed is never more than 30 minutes out of date. The home location you see above is actually the Morton Arboretum; my version of the script uses the latitude and longitude of my house.

Line 20 defines the planets list, and Lines 23–27 create a pair of dictionaries, pos and const, which contain the AltAz position and constellation of each planet. The get_body function (Line 26) gets the position, and the get_constellation function (Line 27) uses that position to figure out the constellation the body is in.

Lines 30–33 create the list of output lines, and Lines 36–38 use the run function of the subprocess module to send the output lines to TerminalWidget. The tw list contains both the full path to the TerminalWidget executable and all the options passed to it. The input parameter to run is the previously defined output, converted to a single string separated by linefeeds and encoded as bytes.

Line 40 is basically a debugging line that I’ve left in for future development. While writing planets, I had Lines 36–38 commented out and Line 40 uncommented so I could see the results immediately in the Terminal.

planets is run by launchd every 30 minutes, on the hour and half-hour, via this launch agent, com.leancrew.planets.plist:

xml:<br>1:<br>2:<br>3:<br>4:<br>5: Label<br>6: com.leancrew.planets<br>7: ProgramArguments<br>8:<br>9: /path/to/python<br>10: /path/to/planets<br>11:<br>12:...

planets line lines widget output function

Related Articles