biome
biome copied to clipboard
Bountiful Interface to Open Meteo for Emacs
#+TITLE: BIOME - Bountiful Interface to Open Meteo for Emacs
[[https://melpa.org/#/biome][file:https://melpa.org/packages/biome-badge.svg]]
Interface to [[https://open-meteo.com/][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.
[[./img/report.png]]
- Installation The package is available on MELPA. Install it however you normally install packages, I prefer [[https://github.com/jwiegley/use-package][use-package]] and [[https://github.com/radian-software/straight.el][straight.el]]: #+begin_src emacs-lisp (use-package biome :straight t) #+end_src
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: #+begin_src emacs-lisp (setq biome-api-try-parse-error-as-response t) #+end_src
Second, somehow =
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 [[https://open-meteo.com/en/docs][API docs]] for more detailed descriptions.
[[./img/root.png]]
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".
[[./img/query.png]]
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 [[https://open-meteo.com/en/docs/geocoding-api][API for those]] as well) or select a location from =biome-query-coords=. Example configuration: #+begin_src emacs-lisp (setq biome-query-coords '(("Helsinki, Finland" 60.16952 24.93545) ("Berlin, Germany" 52.52437 13.41053) ("Dubai, UAE" 25.0657 55.17128))) #+end_src
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 =
Press =RET= after you've configured the query to call the API. If something goes wrong, it will output an error, such as: #+begin_example Open Meteo has returned an error. Error: (error http 400) Reason: Timezone is required #+end_example
Or it will open the results table (the first screenshot).
=tabulated-list= doesn't support horizontal scrolling, so press =c= to toggle columns' visibility.
[[./img/columns.png]]
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: #+begin_src emacs-lisp (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)))) #+end_src
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 [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Hooks-for-Loading.html#index-with_002deval_002dafter_002dload][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: #+begin_src emacs-lisp (setq biome-grid-format (seq-filter (lambda (f) (not (eq (car-safe (nth 2 f)) 'gradient))) biome-grid-format)) #+end_src
Turn off highlighting of the current hour or day as follows: #+begin_src emacs-lisp (setq biome-grid-highlight-current nil) #+end_src
- 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.
[[./img/multi.png]]
(/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: #+begin_src emacs-lisp (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))))) #+end_src 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 [[https://open-meteo.com/en/docs][API docs]]. I've implemented [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Parsing-HTML_002fXML.html][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 [[https://github.com/magit/transient/][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 [[https://www.gnu.org/software/emacs/manual/html_mono/widget.html][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 [[https://lars.ingebrigtsen.no/2022/04/13/more-vtable-fun/][vtable]] comes only in Emacs 29, so I used [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Tabulated-List-Mode.html][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 [[https://www.windy.com/][Windy]]. Formatting for things like air quality variables is probably all over the place, so take the red color with a grain of salt.