# tlohde

six months in a can

01:43 05/08/2026
2282 words
contents

Like many projects, this one was abandonned for a while.

But it’s (mostly) done now.

solarigraphy

Solarigraphy is the photographic practice of using a pinhole camera to capture an image of the sun’s path over days-weeks-months-years. Typically it uses regular B&W photo paper[1] for capturing the image. And processing is gloriously simple: scan the image, invert it left-to-right, and invert the colours. No red-light or chemistry required.

I’ve had a handful of successful attempts, and just as many unsuccessful ones[2]. The unsuccessful, along with my impatience at waiting six months, motivated me to write some wobbly code to simulate these.

To model a solarigraph we need (a) coordinates and (b) dates. From those we can generate the sun’s path; get hold of some whether data; determine what the horizon looks like. Then cobble them together into a single plot.

Onwards.

the sun’s path

Every day the sun draws a smooth curve across the sky that a well-placed solarigraph is primed to capture. First, a brief refresher on what the sun does…

In the northern hemisphere, at some point in March and at another point in September, the sun rises in the East, and gradually climbs to its zenith[3] while moving round towards the South, after which it descends and moves to the West, before then falling behind horizon at sunset (Fig 0).

A graph of solar elevation against azimuth (or compass bearing) showing the path of the sun on the summer and winter solstice and the spring/autumn equinox
Fig 0: Solar paths for the summer (dot-dash) and winter (dotted) solstices, along with the spring (and autumn, though it's not been labelled as such) equinox (solid), as seen in Edinburgh, UK (55.96°N 3.12°W)

In summer:

In winter:

Just how high, and how north of west, very much depends on one’s latitude.

Another thing that depends on one’s latitude and longitude, is what the horizon looks like…

the horizon

At sea, or in the Netherlands, the horizon appears to be a straight line. In the heart of the Dolomites or in a dense urban area, the horizon - that line where the sky stops and land and buildings and trees start - is not straight (Fig 1).

2026-07-13T20:55:17.447747 image/svg+xml Matplotlib v3.11.0, https://matplotlib.org/
Fig 1: Horizons in London (top); Isortuarsuup Sermia, Greenland (middle) and Tarawa, Kiribati (bottom). All plots to same scale. Elevation data: for Greenland and Kiribati - Copernicus WorldDEM-90 © DLR e.V. 2010-2014 and © Airbus Defence and Space GmbH 2014-2018 provided under COPERNICUS by the European Union and ESA; London - LIDAR Composite Digital Surface Model (DSM) - 2m by the Environment Agency. Contains public sector information licensed under the Open Government Licence v3.0.

Sometimes, as I’m sure you’ve seen, the sun is very much above the horizon in the sense that it is daytime, but you also can’t see it, it is below your own personal horizon. Or in the case of a distant mountain, the sun is above the nautical horizon, but below the topographic horizon.

Again, a well placed solarigraph can handle this. And so must a simulated one.

Another thing that influences whether or not one can see the sun…is the weather.

the weather

Because this is a realistic[4] simulation, it relies on weather observations not forecasts. Open-Meteo provide a historical weather API,[5] which can provide hourly measurements of total cloud cover (as a fraction between 0 and 1) from 1940-yesterday over a 0.25° grid. And, in the spirit of choosing-the-path-of-least-resistance, we’ll use the cloud cover fraction to vary the opacity of the sun paths (Fig 2).

figure with two axes. top time series of cloudcover over the course of a day; bottom solar elevation against azimuth at 30 minute intervals over the course of the same day with the opacity of each point varying with the cloud cover 2026-07-24T08:50:16.433315 image/svg+xml Matplotlib v3.11.0, https://matplotlib.org/
Fig 2: Cloud cover (top) and the sun's path (bottom) over Longyearbyen, Svalbard on on 21st April 2026. Dots in the bottom panel are spaced at 30 minute intervals, cloud cover observations (top panel) are hourly. The opacity/transparency of the sun path markers are linearly interpolated from the nearest (in time) cloud cover observations. Also, note how at this latitude and time of year, the sun does not set. Weather data by Open-Meteo.com CC-BY 4.0

projecting

The above figures have all plotted both solar elevation, θ\theta, and solar azimuth, ϕ\phi, in degrees on a linear scale, from North all the way back round to North. But, what goes on inside a, cylindrical with radius RR,[6] pinhole camera isn’t linear. So we need to map solar azimuth and elevation to paper-inside-the-camera coordinates.[7]

Left-to-right is reasonably straightforward, although this took me longer than I care to admit to wrap my head around.

x=2R(ϕ180)x = 2 R (\phi - 180^{\circ})

This is a linear relationship.

For a pinhole camera facing due south (180°, π\pi) only light originating from the east (90°, π2\frac{\pi}{2}) round to the west (270°, 3π2\frac{3\pi}{2}) are theoretically capable of entering the camera. The length of paper inside the camera is 2πR2 \pi R.

The above equations maps east to π-\pi, and west to π\pi, for a total span of 2πR2 \pi R. In reality, however, a field of view of ~150° is more reasonable. Additionally, a bit of modular arithmetic allows us to point the pinhole in whatever direction, γ\gamma, we fancy

ϕshift=(ϕγ+180) mod 360\phi_{shift} = (\phi - \gamma + 180^{\circ}) \text{ mod } 360^{\circ}

Up-and-down is a bit more involved, since the horizontal component, yhy_h, of the distance from the pinhole to the paper depends on the azimuth

yh=2Rcos(180ϕshift)y_h = 2 R \cos(180^{\circ} - \phi_{shift})

and the resulting vertical distance from the pinhole, yy, is a function of both elevation and azimuth

y=yhtan(θ)=2Rcos(180ϕshift)tan(θ)\begin{aligned} y &= y_h \tan(\theta)\\ &= 2 R \cos(180^{\circ} - \phi_{shift}) \tan(\theta) \end{aligned}

This projection process gives rise to a few, let’s call them, features (Fig 3). Irrespective of how far beyond the camera’s azimuth range sunrise/set actually is, the projected image gives the impression of a sunrise/set at the edges of this range. This is because at these extremes yh=0y_{h} = 0, and so, regardless of the solar elevation, θ\theta, the yy in paper-coordinates goes to zero. This gives the apperance of solar paths at different times of year converging to a point.

Another quirk, is that terrain is more subdued — less dramatic, especially away from the centre. In Fig 3 this can be seen in the two peaks in the south-west that are at approximately the same elevation angle (the more western one is slightly lower) in the unprojected image, but once projected the more western one is markedly lower, and less peak-y.

This is a problem with tan\tan. And it is particularly evident at tropical latitudes where solar elevations can sneak up to 90°,[8] and tan(90)\tan (90^{\circ}) is kind of undefined. So, in these simulated solarigraphs there is a need to clip the elevations to something sensible, like 76°, or arctan(4)\arctan(4).[9]

Side-by-side figure showing two solarigraphs, on the left one that is unprojected with elevations in degrees, and showing all azimuths North-to-North. The other in projected coordinates with a 140 degree field of view centered on south 2026-07-23T21:59:07.793999 image/svg+xml Matplotlib v3.11.0, https://matplotlib.org/
Fig 3: An unprojected (left) solarigraph showing solar paths over Semisopochnoi Island (51.95 N°, 179.62 E°) on the summer solstice, autumn equinox and winter solstice in 2025, and how it apperas once projected (right). Note the reduced field-of-view (90°–270°) in the projected scene, which corresponds to the unshaded area on the left.

styling

Firstly, move the azimuth axis and the cardinal[10] points the top. Secondly, borrow the colours for land, sky and sun from the small handful of real solarigraphs that I’ve made.[11] And tah-dah, you can now pick your theme and see the output in Fig 4.

2026-07-24T15:35:27.069284 image/svg+xml Matplotlib v3.11.0, https://matplotlib.org/
Fig 4: A styled solarigraph looking south-ish (195°) over Sandvinvatnet from Odda, Norway winter 2025-26. The peak to the SW is Jordalsnuten (1043 m), from this vantage we can't quite see the summit. And it looks like it was pretty cloudy in February. Weather data by Open-Meteo.com CC-BY 4.0; Elevation data from Copernicus WorldDEM-90 © DLR e.V. 2010-2014 and © Airbus Defence and Space GmbH 2014-2018 provided under COPERNICUS by the European Union and ESA

Oh, and, add some credits for the data sources and me[12], and perhaps a title with the coordinates and time range being shown.

bundling

cli

At first I thought I’d bundle my code up in such a way that someone could use pipx to install the a little cli. I used typer for this. And well, that is a thing that can be done…

installation

solarigraph is now installed.

usage

Run with the following command: solarigraph make. See solarigraph make --help for a full list of arguments and options. Do not be scared of all the parameters, I have tried to give most of them sensible defaults. For example…

solarigraph make 63.83034 -50.06830 2026-04-01 2026-05-30 --facing 90

will output a file named something like sgraph_63.83-50.06.png which should give you a glimpse into early spring at Isortuarsuup Sermia.

api

Now we’re cooking. It was reasonably straightforward to set up a fastAPI, and so if you don’t feel like downloading it yourself — no bother.

The fiddly thing, for me at least, was the requisite JS for the below.

Will this work in every instance. Probably not.

There is some validation here:

Click a point on the map; select some dates,[13] and which way you’re looking[14]; and how far you want to search for the horizon[15]

conclusion?

It’s worth saying that, as usual Darling Reader, this exercise was more[16] for me. If you enjoyed some of it—any of it—I am thrilled. Truly. But it was mainly for me to play and learn some stuff.

And I played and I learnt some stuff.

Is it perfect. Nope. Are there a bucket-load of edge-cases that bork the whole enterprise. Yep.

And post.

footnotes


  1. the sort used to make B&W prints in a dark room ↩︎

  2. for reasons unknown, or some busy-body taking it off the lamppost-or-whatever I lashed it to ↩︎

  3. it’s highest point in the sky on a given day ↩︎

  4. whatever that means ↩︎

  5. using ERA5 reanalysis data ↩︎

  6. let R=1R=1 for convenience, y’know ↩︎

  7. and start working in radians ↩︎

  8. there’s a cool name for it: Lāhainā Noon ↩︎

  9. no reason other than I tried a few values, and across a few latitudes and this seemed good enough ↩︎

  10. and intercardinal ↩︎

  11. and, I should probably make a few more palettes ↩︎

  12. because i’m fabulous I made the damn thing ↩︎

  13. maybe from one solstice to the next ↩︎

  14. 0: North; 90: East; 180: South; 270 West. And everything in between ↩︎

  15. don’t bother going over 50 ↩︎

  16. wholly ↩︎