BIOME - Bountiful Interface to Open Meteo for Emacs

BIOME - Bountiful Interface to Open Meteo for Emacs

Interface to Open Meteo for Emacs. The service provides weather forecasts, historical weather data, climate change projections, and more.

The service is AGPL-licensed; the hosted API is free for non-commercial use if you make less than 10000 requests per day.

Installation

The package is available on MELPA. Install it however you normally install packages, I prefer use-package and straight.el:

(use-package biome
  :straight t)

Or clone the repository, add it to load-path, and require the package.

Issues with termux?

I’ve been trying to run this package on termux and had some issues.

First, for some reason request.el throws a successfully parsed response as an error. Use this as a workaround:

(setq biome-api-try-parse-error-as-response t)

Second, somehow <tab> is not the same as <TAB>. The following might be necessary:

(setq biome-query-tab-key "<TAB>")

Be sure to add that before the package initialization.

Usage

The main entry point is M-x biome. Each item under “Open Meteo Data” corresponds to a particular endpoint of the service. For instance, M-x biome ww is a generic weather forecast. Check out the API docs for more detailed descriptions.

Each of these items opens a query interface. A query consists of “global” variables, such as location, units, etc., and “group variables”. Groups are usually “hourly” and “daily”.

Global variables must always include a location (section “Select Coordinates or City”). To enter a location, you can either enter latitude and longitude (Open Meteo has an API for those as well) or select a location from biome-query-coords. Example configuration:

(setq biome-query-coords
      '(("Helsinki, Finland" 60.16952 24.93545)
	("Berlin, Germany" 52.52437 13.41053)
	("Dubai, UAE" 25.0657 55.17128)))

A timezone (“Settings” > “Timezone”) may not be required, but be sure to set it because the default one is UTC+0.

The current group is switched with <tab>. Each group’s section has a set of variables that can be toggled on and off, such as temperature, precipitation, etc. Check out the API docs if you’re interested in the meaning of more esoteric ones.

Press RET after you’ve configured the query to call the API. If something goes wrong, it will output an error, such as:

Open Meteo has returned an error.
Error: (error http 400)
Reason: Timezone is required

Or it will open the results table (the first screenshot).

tabulated-list doesn’t support horizontal scrolling, so press c to toggle columns’ visibility.

Press c or invoke M-x biome-grid-export-csv to export the results in CSV format.

More configuration

To save a query for later, press P in the root of the query interface. This will generate a definition like this:

(biome-def-preset biome-query-preset-177
  ((:name . "Weather Forecast")
   (:group . "hourly")
   (:params
    ("hourly" "windgusts_10m" "windspeed_10m" "cloudcover" "surface_pressure" "weathercode" "snowfall" "showers" "rain" "relativehumidity_2m" "temperature_2m")
    ("longitude" . 24.93545)
    ("latitude" . 60.16952))))

Add this somewhere in your config after the package is loaded, e.g., in the :config section of the use-package form or wrapped in with-eval-after-load. Running M-x biome-query-preset-177 will create a query interface with this preset.

Alternatively, use the add-to-list form (generated below the biome-def-preset form). Presets added that way will show up in M-x biome-presets or “Presets” in M-x biome.

Table formatting can be configured with biome-grid-format; check the docstring for more information. For instance, if you want to disable all gradients:

(setq biome-grid-format (seq-filter (lambda (f) (not (eq (car-safe (nth 2 f))
							 'gradient)))
				    biome-grid-format))

Turn off highlighting of the current hour or day as follows:

(setq biome-grid-highlight-current nil)

Composite queries

The package also allows executing multiple queries at once to join their results. This can be useful for comparing weather in different locations or for viewing different reports about the same location.

Run M-x biome-multi to invoke the-multi query dialog.

(yes, I’ve switched to a light theme since the time of the previous screenshot)

Pressing a invokes the standard query dialog, where pressing RET returns to the root dialog, adding the query to the list. Pressing RET in the root dialog executes the queries in the list.

Queries are executed concurrently. The results are shown if all queries have been successfully completed.

P generates a preset defintion for the current query:

(biome-def-multi-preset biome-query-preset-601
  (((:name . "Air Quality")
    (:group . "hourly")
    (:params
     ("hourly" "uv_index" "european_aqi")
     ("longitude" . 24.93545)
     ("latitude" . 60.16952)))
   ((:name . "Weather Forecast")
    (:group . "hourly")
    (:params
     ("hourly" "weathercode" "snowfall" "showers" "rain" "temperature_2m")
     ("longitude" . 24.93545)
     ("latitude" . 60.16952)))))

Just note that the macro is called biome-def-multi-preset.

Implementation notes

This isn’t the most complicated thing I’ve done, but it’s probably the most over-engineered one.

As you may have guessed, the interfaces mirror the API docs. I’ve implemented parsing of these HTMLs in biome-api-parse--generate, which generates the value of biome-api-data. Initially, it downloaded the HTML pages by itself, but - imagine that - the website was migrated to Svelte after I implemented maybe 80% of the parsing logic, and the Svelte version populates the accordions via JavaScript. So, as of now, the function requires opening the website in the browser, manually toggling all the accordions, and copying the HTML from DevTools. Fortunately, the parsing is a one-off operation.

Then, the interface… I like transient.el, so I wanted to make the interface generated dynamically from biome-api-data, which turned out harder than I expected. I probably should’ve just used widget.el.

Generating sensible keys was a challenge. I’ve made an algorithm in biome-query--unique-keys that sort of works well.

And as for populating transient prefixes, I tried to use :setup-children in a few places, but it’s not general enough, namely, it doesn’t seem to support specifying :class for child groups… So I ended up overriding transient--layout in the prefix setup. This doesn’t seem to have any undesirable side effects.

Also, the only way I found to use custom infix classes in these dynamic definitions was to eval transient-define-infix for each required place. Unfortunately, that adds a lot of stuff to the interactive functions namespace.

Getting to the results display, Lars Ingebrigtsen’s vtable comes only in Emacs 29, so I used tabulated-list. The only disadvantage of the latter is the lack of horizontal scroll support, which can be worked around by hiding columns with biome-grid-columns.

Most variables are formatted with a gradient, colors for which were mostly inspired by Windy. Formatting for things like air quality variables is probably all over the place, so take the red color with a grain of salt.