I’m happy to announce that the {skytrackr} package is on its way to CRAN. The {skytrackr} R package provides a convenient template fitting methodology and a Bayesian based optimization approach to estimate locations from light profiles. In my research together with Lyndon Kearsley we’ve used geolocation by light extensively to track swifts (an example light profile, as recorded during a year by a micro-logger on the back of a swift, is shown below).

Experience has taught me that the processing this data is less than straightforward. Although several R packages exist to tackle these tasks, e.g. {FlightR} and {GeoPressureR}, they often require a rather complex workflow. This limits the ability of people who are less comfortable with code from taking on this task and quickly exploring the data. With the {skytrackr} package I’ve tried to simplify the data processing as much as possible. Conceptually, the package is closest to the existing {FlightR} package.
Contrary to the common twilight method, a template matching approach uses a larger part of the light profile (either a section during twilight or more). Adding data should increase the robustness to noise in the data, as there is more data to constrain the relationship between location and light levels.
The {skytrackr} package fits light data, as measured throughout a given day, to a model representation defined by latitude, longitude and sky conditions (using the {skylight} package). In the below figure the grey dots represent measured data, while the three coloured lines show the impact of changing latitude, longitude or both. The blue line shows the impact of changing the longitude of the {skylight} model, moving the diurnal light cycle back and forth in time. Changing the latitude changes the “steepness” of the profile. When you minimize the error between the data and the modelled line (changing both latitude and longitude) you get the best estimate of the location of the recorded light profile, i.e. the red line.

The modelled red line still overestimates values relative to the observed grey dots. This is due to the fact that the sky conditions were kept constant. Sky conditions influence how bright the sky is, with more clouds lowering the amount of light. However, other factors such as the sensitivity of the sensor or it being covered by fur/feathers can also decrease the amount of available light measured. So, sky conditions can be seen as a catch all for environmental conditions which might influence the available light.
Template matching is not insensitive to inherent physical limits of the method. For example, dates around the equinoxes are by definition uncertain. Therefore the package allows for location estimates to be constraint to positions on land (through a land mask), and movement distances are limited to realistic behaviour (using a step-selection function). Combining the template matching with these additional constraints allows for an almost hands-off experience in estimating location from light levels. The workflow therefore follows only a few steps, as described below.
Location estimation workflow
First, you read in the data.
# read in the demo lux file
df <- stk_read_lux(
system.file("extdata/cc876.lux", package="skytrackr")
)
Second, you screen the data for outliers / noise during twilight or the day, dropping the bad values (filter=TRUE). The cleaned version of the light profile above is shown below.
df <- df |> stk_screen_twl(filter = TRUE)

Next, you define a region of interest by creating a land mask.
mask <- stk_mask(
bbox = c(-20, -40, 60, 60), #xmin, ymin, xmax, ymax
buffer = 150, # in km
resolution = 0.5 # in degrees
)
Then, you define a step-selection function.
# define a step selection distribution
ssf <- function(x, shape = 0.9, scale = 100, tolerance = 1500){
# normalize over expected range with km increments
norm <- sum(stats::dgamma(1:tolerance, shape = shape, scale = scale))
prob <- stats::dgamma(x, shape = shape, scale = scale) / norm
return(prob)
}
Finally, you call the main skytrackr() function which deals with the template matching. Aside from the start location, step selection function and the mask all other parameters use the defaults. These defaults should generate overall good results within a reasonable time frame (depending on your CPU from <1h to 2h).
locations <- df |>
skytrackr(
start_location = c(51.08, 3.73),
mask = mask,
step_selection = ssf,
plot = TRUE
)
During this process, if plot is set to TRUE, you will get feedback in the form of a map showing you all matched locations and a lime green search area for the next suitable step.

If the process has completed you can plot all locations using a convenient plotting function. Setting the parameter dynamic=TRUE will generate an interactive map for further data inspection.
# static map
locations |> stk_map(bbox = c(-20, -40, 60, 60))
# interactive/dynamic map
locations |> stk_map(dynamic = TRUE)

For a detailed description with academic references I refer to the package vignette and function documentation. Note, as with all BlueGreen Labs software, this is first and foremost a tool to facilitate our own research on swifts, which are an ideal species in the context of light logging, and this solution is therefore tailored toward this application (caveats apply for other species).