biome: update API

This commit is contained in:
Pavel Korytov 2024-04-20 21:32:40 +03:00
parent baeb36cae7
commit b264e51e66
5 changed files with 5255 additions and 1232 deletions

File diff suppressed because it is too large Load diff

View file

@ -88,6 +88,10 @@
(:url . "https://open-meteo.com/en/docs/ensemble-api") (:url . "https://open-meteo.com/en/docs/ensemble-api")
(:description . "Weather information since 1940") (:description . "Weather information since 1940")
(:key . "e")) (:key . "e"))
((:name . "Previous Runs API")
(:url . "https://open-meteo.com/en/docs/previous-runs-api")
(:description . "Weather Forecasts from Previous Days to Compare Run-To-Run Performance")
(:key . "v"))
((:name . "Climate Change") ((:name . "Climate Change")
(:url . "https://open-meteo.com/en/docs/climate-api") (:url . "https://open-meteo.com/en/docs/climate-api")
(:description . "Climate change projections") (:description . "Climate change projections")
@ -149,11 +153,61 @@
"Replace these variable defintions with the given ones.") "Replace these variable defintions with the given ones.")
(defconst biome-api-parse--add-settings (defconst biome-api-parse--add-settings
`(((:param . ("elevation" . ((:name . "Elevation") `(("settings" . (((:param . ("elevation" . ((:name . "Elevation")
(:type . float)))) (:type . float))))
(:pages . ("Weather Forecast" "DWD ICON" "NOAA GFS & HRRR" (:pages . ("Weather Forecast"
"MeteoFrance" "ECMWF" "JMA" "MET Norway" "GEM" "Historical Weather" "DWD ICON (Germany)"
"NOAA GFS & HRRR (U.S.)"
"MeteoFrance"
"ECMWF"
"JMA (Japan)"
"MET (Norway)"
"GEM (Canada)"
"BOM (Australia)"
"CMA (China)"
"Historical Weather"
"Ensemble Models"))))) "Ensemble Models")))))
("coordinates and time" . (((:param . ("start_date" . ((:name . "Start date")
(:type . date))))
(:pages . ("Weather Forecast"
"DWD ICON (Germany)"
"NOAA GFS & HRRR (U.S.)"
"MeteoFrance"
"ECMWF"
"JMA (Japan)"
"MET (Norway)"
"GEM (Canada)"
"BOM (Australia)"
"CMA (China)"
"Ensemble Models"
"Previous Runs API"
"Marine Forecast"
"Flood")))
((:param . ("end_date" . ((:name . "End date")
(:type . date))))
(:pages . ("Weather Forecast"
"DWD ICON (Germany)"
"NOAA GFS & HRRR (U.S.)"
"MeteoFrance"
"ECMWF"
"JMA (Japan)"
"MET (Norway)"
"GEM (Canada)"
"BOM (Australia)"
"CMA (China)"
"Ensemble Models"
"Previous Runs API"
"Marine Forecast"
"Flood")))))))
;; TODO make empty when the website is fixed
(defconst biome-api-parse--field-names-default
'(("tilt" . "Panel Tilt (0° horizontal)")
("azimuth" . "Panel Azimuth (0° S, -90° E, 90° W)")))
;; TODO make empty when the website is fixed
(defconst biome-api-parse--fix-ids
'(("past_minutely_15s" . "past_minutely_15")))
(defun biome-api-parse--fix-string (string) (defun biome-api-parse--fix-string (string)
"Remove extra spaces and newlines from STRING." "Remove extra spaces and newlines from STRING."
@ -163,27 +217,47 @@
" " " "
string))) string)))
(defun biome-api-parse--table-variables (section)
"Parse variable in table in SECTION.
SECTION is a DOM element. Return a list of sections as defined by
`biome-api-parse--page', one section per each table row."
(when-let* ((table (car (dom-by-class section "table-responsive")))
(table-rows (dom-by-tag table 'tr)))
(cl-loop for row in table-rows
for name = (dom-text (car (dom-by-tag row 'td)))
for fields = (biome-api-parse--page-variables row)
;; do (cl-loop for field in fields
;; do (setf (alist-get :name field)
;; (format "%s (%s)" (alist-get :name field) name)))
collect `((:name . ,name)
(:fields . ,fields)))))
(defun biome-api-parse--page-variables (section) (defun biome-api-parse--page-variables (section)
"Parse variables from SECTION. "Parse variables from SECTION.
SECTION is a DOM element. Return a list of fields as defined by SECTION is a DOM element. Return a list of fields as defined by
`biome-api-parse--page'." `biome-api-parse--page'."
(let ((elements (dom-search section (lambda (el) (not (stringp el))))) (let ((elements (dom-search section (lambda (el) (not (stringp el)))))
fields field-names field-id-mapping) (field-names (copy-tree biome-api-parse--field-names-default))
fields field-id-mapping)
(cl-loop for elem in elements (cl-loop for elem in elements
for dom-id = (seq-some for dom-id = (seq-some
(lambda (v) (unless (string-empty-p v) v)) (lambda (v) (unless (string-empty-p v) v))
(list (dom-attr elem 'id) (list (dom-attr elem 'id)
(dom-attr elem 'name))) (dom-attr elem 'name)))
for id = (seq-some for id = (let ((id (seq-some
(lambda (v) (unless (string-empty-p v) v)) (lambda (v) (unless (string-empty-p v) v))
(list (let ((val (dom-attr elem 'value))) (list (let ((val (dom-attr elem 'value)))
(unless (member val '("true" "false")) val)) (unless (member val '("true" "false")) val))
dom-id)) dom-id))))
(alist-get id biome-api-parse--fix-ids
id nil #'equal))
if (and (member (dom-tag elem) '(input select)) if (and (member (dom-tag elem) '(input select))
(not (equal id dom-id))) (not (equal id dom-id)))
do (push (cons dom-id id) field-id-mapping) do (push (cons dom-id id) field-id-mapping)
if (member id biome-api-parse--exclude-ids) if (or (member id biome-api-parse--exclude-ids)
(equal (dom-attr elem 'type) "hidden"))
do (null nil) ; how to do nothing? :D do (null nil) ; how to do nothing? :D
else if (member id biome-api-parse--float-ids) else if (member id biome-api-parse--float-ids)
do (push (cons id '((:type . float))) fields) do (push (cons id '((:type . float))) fields)
@ -195,11 +269,15 @@ SECTION is a DOM element. Return a list of fields as defined by
do (push do (push
(cons id (cons id
`((:type . select) `((:type . select)
(:options . ,(mapcar (:options . ,(seq-filter
(lambda (item)
(not (equal (cdr item)
"- (default)")))
(mapcar
(lambda (opt) (cons (dom-attr opt 'value) (lambda (opt) (cons (dom-attr opt 'value)
(biome-api-parse--fix-string (biome-api-parse--fix-string
(dom-texts opt)))) (dom-texts opt))))
(dom-by-tag elem 'option))))) (dom-by-tag elem 'option))))))
fields) fields)
else if (eq (dom-tag elem) 'label) else if (eq (dom-tag elem) 'label)
do (push (cons (or (cdr (assoc (dom-attr elem 'for) field-id-mapping)) do (push (cons (or (cdr (assoc (dom-attr elem 'for) field-id-mapping))
@ -256,6 +334,10 @@ Return a list of sections as defined by `biome-api-parse--page'."
(push `((:name . ,item-name) (push `((:name . ,item-name)
(:children . ,(biome-api-parse--page-pills item))) (:children . ,(biome-api-parse--page-pills item)))
res) res)
else if (dom-by-class item "table-responsive") do
(push `((:name . ,item-name)
(:children . ,(biome-api-parse--table-variables item)))
res)
else do (push `((:name . ,item-name) else do (push `((:name . ,item-name)
(:fields . ,(biome-api-parse--page-variables item))) (:fields . ,(biome-api-parse--page-variables item)))
res)) res))
@ -267,6 +349,8 @@ Return a list of sections as defined by `biome-api-parse--page'."
The return value is as defined by `biome-api-parse--page'." The return value is as defined by `biome-api-parse--page'."
(or (when-let ((accordion (biome-api-parse--page-accordion section))) (or (when-let ((accordion (biome-api-parse--page-accordion section)))
`((:children . ,accordion))) `((:children . ,accordion)))
(when-let ((table (biome-api-parse--table-variables section)))
`((:children . ,table)))
(when-let ((variables (biome-api-parse--page-variables section))) (when-let ((variables (biome-api-parse--page-variables section)))
`((:fields . ,variables))))) `((:fields . ,variables)))))
@ -318,29 +402,39 @@ NAME is the page name as given in `biome-api-parse--urls'."
,@data))) ,@data)))
;; Extract the model section from the hourly accordion and add it to ;; Extract the model section from the hourly accordion and add it to
;; the root section. ;; the root section.
(when-let ((models-data (biome-api-parse--postprocess-extract-section (when-let* ((models-data (biome-api-parse--postprocess-extract-section
sections "models" t))) sections "models" t))
(models-section (cdr models-data)))
(setq sections (append (car models-data) (setq sections (append (car models-data)
(list (cdr models-data))))) (list models-section))))
;; Add settings ;; Likewise with the 15-minutely section.
(when-let ((settings-data (biome-api-parse--postprocess-extract-section (when-let* ((15-minutely-data (biome-api-parse--postprocess-extract-section
sections "settings"))) sections "15-minutely" t))
;; Merge settings and "Location and Time" (15-minutely-section (cdr 15-minutely-data)))
(setq sections (append (car 15-minutely-data)
(list 15-minutely-section))))
(when-let ((location-data (biome-api-parse--postprocess-extract-section (when-let ((location-data (biome-api-parse--postprocess-extract-section
sections "location and time" t))) sections "location and time" t)))
(setf (alist-get :fields settings-data) (setf (alist-get :name location-data)
(append (alist-get :fields location-data) "Select Coordinates and Time"))
(alist-get :fields settings-data))))
(cl-loop for var in biome-api-parse--add-settings ;; Add settings
(cl-loop
for (section-name . add-vars) in biome-api-parse--add-settings
do (when-let ((settings-data (biome-api-parse--postprocess-extract-section
sections section-name)))
(cl-loop for var in add-vars
if (member name (alist-get :pages var)) if (member name (alist-get :pages var))
do (push (copy-tree (alist-get :param var)) do (setf (alist-get :fields (cdr settings-data))
(alist-get :fields (cdr settings-data)))) (cons (copy-tree (alist-get :param var))
(alist-get :fields (cdr settings-data)))))
;; Fix forecast_days for Flood API ;; Fix forecast_days for Flood API
(when (equal name "Flood") (when (equal name "Flood")
(let ((forecast-days (alist-get "forecast_days" (when-let ((forecast-days (alist-get "forecast_days"
(alist-get :fields (cdr settings-data)) (alist-get :fields (cdr settings-data))
nil nil #'equal))) nil nil #'equal)))
(setf (alist-get :max forecast-days) 210)))) (setf (alist-get :max forecast-days) 210)))))
;; Add section-specific URL params ;; Add section-specific URL params
;; XXX I do not know why this doesn't work without returning ;; XXX I do not know why this doesn't work without returning
;; sections from the loop ;; sections from the loop
@ -468,7 +562,8 @@ them to biome-api-data.el."
(let ((timezones (biome-api-parse--timezones))) (let ((timezones (biome-api-parse--timezones)))
(cl-loop for datum in biome-api-parse--urls (cl-loop for datum in biome-api-parse--urls
do (biome-api-parse--datum datum)) do (biome-api-parse--datum datum))
(let ((buffer (generate-new-buffer "*biome-generated*"))) (let ((buffer (generate-new-buffer "*biome-generated*"))
(indent-tabs-mode nil))
(with-current-buffer buffer (with-current-buffer buffer
(emacs-lisp-mode) (emacs-lisp-mode)
(insert (pp-to-string (insert (pp-to-string

View file

@ -30,15 +30,18 @@
(defconst biome-api--default-urls (defconst biome-api--default-urls
'(("Weather Forecast" . "https://api.open-meteo.com/v1/forecast") '(("Weather Forecast" . "https://api.open-meteo.com/v1/forecast")
("DWD ICON" . "https://api.open-meteo.com/v1/dwd-icon") ("DWD ICON (Germany)" . "https://api.open-meteo.com/v1/dwd-icon")
("NOAA GFS & HRRR" . "https://api.open-meteo.com/v1/gfs") ("NOAA GFS & HRRR (U.S.)" . "https://api.open-meteo.com/v1/gfs")
("MeteoFrance" . "https://api.open-meteo.com/v1/meteofrance") ("MeteoFrance" . "https://api.open-meteo.com/v1/meteofrance")
("ECMWF" . "https://api.open-meteo.com/v1/ecmwf") ("ECMWF" . "https://api.open-meteo.com/v1/ecmwf")
("JMA" . "https://api.open-meteo.com/v1/jma") ("JMA (Japan)" . "https://api.open-meteo.com/v1/jma")
("MET Norway" . "https://api.open-meteo.com/v1/metno") ("MET (Norway)" . "https://api.open-meteo.com/v1/metno")
("GEM" . "https://api.open-meteo.com/v1/gem") ("GEM (Canada)" . "https://api.open-meteo.com/v1/gem")
("BOM (Australia)" . "https://api.open-meteo.com/v1/bom")
("CMA (China)" . "https://api.open-meteo.com/v1/cma")
("Historical Weather" . "https://archive-api.open-meteo.com/v1/archive") ("Historical Weather" . "https://archive-api.open-meteo.com/v1/archive")
("Ensemble Models" . "https://ensemble-api.open-meteo.com/v1/ensemble") ("Ensemble Models" . "https://ensemble-api.open-meteo.com/v1/ensemble")
("Previous Runs API" . "https://previous-runs-api.open-meteo.com/v1/forecast")
("Climate Change" . "https://climate-api.open-meteo.com/v1/climate") ("Climate Change" . "https://climate-api.open-meteo.com/v1/climate")
("Marine Forecast" . "https://marine-api.open-meteo.com/v1/marine") ("Marine Forecast" . "https://marine-api.open-meteo.com/v1/marine")
("Air Quality" . "https://air-quality-api.open-meteo.com/v1/air-quality") ("Air Quality" . "https://air-quality-api.open-meteo.com/v1/air-quality")

View file

@ -499,6 +499,9 @@ column.
ENTRIES is a list of values. COL-KEY is the column key as given by ENTRIES is a list of values. COL-KEY is the column key as given by
the API. UNIT is the unit of the column." the API. UNIT is the unit of the column."
;; Current weather is not a sequence.
(unless (sequencep entries)
(setq entries (list entries)))
(let ((format-def (biome-grid--get-format-def col-key unit))) (let ((format-def (biome-grid--get-format-def col-key unit)))
(mapcar (mapcar
(lambda (entry) (lambda (entry)
@ -709,7 +712,8 @@ by `biome-api-get')."
(defun biome-grid--maybe-highlight-current () (defun biome-grid--maybe-highlight-current ()
"Highlight current hour or day (if hour is not found)." "Highlight current hour or day (if hour is not found)."
(when biome-grid-highlight-current (when (and biome-grid-highlight-current
(length> tabulated-list-entries 1))
(save-excursion (save-excursion
(goto-char (point-min)) (goto-char (point-min))
(let ((needle-datetime (let ((needle-datetime

View file

@ -89,6 +89,9 @@ The format is: (name latitude longitude)."
:type 'string :type 'string
:group 'biome) :group 'biome)
(defconst biome-query--max-sections-for-row 6
"Maximum number of sections to use for `transient-row'.")
(defconst biome-query-groups '("daily" "hourly" "minutely_15" "hourly" "current") (defconst biome-query-groups '("daily" "hourly" "minutely_15" "hourly" "current")
"Name of groups. "Name of groups.
@ -100,7 +103,9 @@ have to be displayed separately.")
(defconst biome-query--split-items '(("timezone" . "time zone") (defconst biome-query--split-items '(("timezone" . "time zone")
("timeformat" . "time format") ("timeformat" . "time format")
("weathercode" . "weather code") ("weathercode" . "weather code")
("iso8601" . "iso 8")) ("iso8601" . "iso 8")
;; I'm used to "c" for "coordinates"
("current weather" . "urrent weather"))
"Items to split into separate words for generating keys.") "Items to split into separate words for generating keys.")
(defconst biome-query--ignore-items '("m" "cm") (defconst biome-query--ignore-items '("m" "cm")
@ -850,7 +855,9 @@ the position of the current section in the `biome-api-data' tree."
,@(thread-last ,@(thread-last
(append (append
fields fields
(when (equal (alist-get :name (car parents)) "Select Coordinates or City") (when (string-match-p
(rx "Select Coordinates")
(alist-get :name (car parents)))
'(coords))) '(coords)))
(seq-map-indexed (seq-map-indexed
(lambda (field idx) (cons field (/ idx biome-query-max-fields-in-row)))) (lambda (field idx) (cons field (/ idx biome-query-max-fields-in-row))))
@ -877,7 +884,9 @@ KEYS is the result of `biome-query--unique-keys'. PARENTS is a
list of parent sections." list of parent sections."
(when sections (when sections
`(["Sections" `(["Sections"
:class transient-row :class ,(if (length> sections biome-query--max-sections-for-row)
'transient-column
'transient-row)
,@(mapcar ,@(mapcar
(lambda (section) (lambda (section)
`(,(gethash (alist-get :name section) keys) `(,(gethash (alist-get :name section) keys)