mirror of
https://github.com/SqrtMinusOne/biome.git
synced 2025-12-10 14:35:13 +03:00
Merge pull request #9 from SqrtMinusOne/update-data
Update API data, add biome-multi-history
This commit is contained in:
commit
804a0576f5
7 changed files with 5434 additions and 1250 deletions
6280
biome-api-data.el
6280
biome-api-data.el
File diff suppressed because it is too large
Load diff
|
|
@ -36,16 +36,19 @@
|
||||||
(defvar biome-api-parse--data nil
|
(defvar biome-api-parse--data nil
|
||||||
"Data parsed from the API docs.")
|
"Data parsed from the API docs.")
|
||||||
|
|
||||||
|
(defvar biome-api-parse--htmls (make-hash-table :test #'equal)
|
||||||
|
"HTML strings of the API docs.")
|
||||||
|
|
||||||
(defconst biome-api-parse--urls
|
(defconst biome-api-parse--urls
|
||||||
'(((:name . "Weather Forecast")
|
'(((:name . "Weather Forecast")
|
||||||
(:url . "https://open-meteo.com/en/docs")
|
(:url . "https://open-meteo.com/en/docs")
|
||||||
(:description . "Seamless integration of high-resolution weather models with up 16 days forecast")
|
(:description . "Seamless integration of high-resolution weather models with up 16 days forecast")
|
||||||
(:key . "ww"))
|
(:key . "ww"))
|
||||||
((:name . "DWD ICON")
|
((:name . "DWD ICON (Germany)")
|
||||||
(:url . "https://open-meteo.com/en/docs/dwd-api")
|
(:url . "https://open-meteo.com/en/docs/dwd-api")
|
||||||
(:description . "German Weather Service ICON model. 15-minutely data for Central Europe")
|
(:description . "German Weather Service ICON model. 15-minutely data for Central Europe")
|
||||||
(:key . "wd"))
|
(:key . "wd"))
|
||||||
((:name . "NOAA GFS & HRRR")
|
((:name . "NOAA GFS & HRRR (U.S.)")
|
||||||
(:url . "https://open-meteo.com/en/docs/gfs-api")
|
(:url . "https://open-meteo.com/en/docs/gfs-api")
|
||||||
(:description . "Forecasts tailored for the US region")
|
(:description . "Forecasts tailored for the US region")
|
||||||
(:key . "wu"))
|
(:key . "wu"))
|
||||||
|
|
@ -57,18 +60,26 @@
|
||||||
(:url . "https://open-meteo.com/en/docs/ecmwf-api")
|
(:url . "https://open-meteo.com/en/docs/ecmwf-api")
|
||||||
(:description . "Open-data forecasts by ECMWF")
|
(:description . "Open-data forecasts by ECMWF")
|
||||||
(:key . "we"))
|
(:key . "we"))
|
||||||
((:name . "JMA")
|
((:name . "JMA (Japan)")
|
||||||
(:url . "https://open-meteo.com/en/docs/jma-api")
|
(:url . "https://open-meteo.com/en/docs/jma-api")
|
||||||
(:description . "Forecasts tailored for Japan")
|
(:description . "Forecasts tailored for Japan")
|
||||||
(:key . "wj"))
|
(:key . "wj"))
|
||||||
((:name . "MET Norway")
|
((:name . "MET (Norway)")
|
||||||
(:url . "https://open-meteo.com/en/docs/metno-api")
|
(:url . "https://open-meteo.com/en/docs/metno-api")
|
||||||
(:description . "Forecasts exclusively for North Europe")
|
(:description . "Forecasts exclusively for North Europe")
|
||||||
(:key . "wn"))
|
(:key . "wn"))
|
||||||
((:name . "GEM")
|
((:name . "GEM (Canada)")
|
||||||
(:url . "https://open-meteo.com/en/docs/gem-api")
|
(:url . "https://open-meteo.com/en/docs/gem-api")
|
||||||
(:description . "Forecasts tailored for North America")
|
(:description . "Forecasts tailored for North America")
|
||||||
(:key . "wa"))
|
(:key . "wa"))
|
||||||
|
((:name . "BOM (Australia)")
|
||||||
|
(:url . "https://open-meteo.com/en/docs/bom-api/")
|
||||||
|
(:description . "Weather forecasts from the Australian Bureau of Meteorology")
|
||||||
|
(:key . "wb"))
|
||||||
|
((:name . "CMA (China)")
|
||||||
|
(:url . "https://open-meteo.com/en/docs/gem-api")
|
||||||
|
(:description . "Weather forecasts from the Chinese Meteorological Administration")
|
||||||
|
(:key . "wc"))
|
||||||
((:name . "Historical Weather")
|
((:name . "Historical Weather")
|
||||||
(:url . "https://open-meteo.com/en/docs/historical-weather-api")
|
(:url . "https://open-meteo.com/en/docs/historical-weather-api")
|
||||||
(:description . "Weather information since 1940")
|
(:description . "Weather information since 1940")
|
||||||
|
|
@ -77,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")
|
||||||
|
|
@ -100,6 +115,7 @@
|
||||||
("Hourly Weather Variables" . "hourly")
|
("Hourly Weather Variables" . "hourly")
|
||||||
("15-Minutely Weather Variables" . "minutely_15")
|
("15-Minutely Weather Variables" . "minutely_15")
|
||||||
("3-Hourly Weather Variables" . "hourly")
|
("3-Hourly Weather Variables" . "hourly")
|
||||||
|
("Current Weather" . "current")
|
||||||
("Hourly Marine Variables" . "hourly")
|
("Hourly Marine Variables" . "hourly")
|
||||||
("Daily Marine Variables" . "daily")
|
("Daily Marine Variables" . "daily")
|
||||||
("Hourly Air Quality Variables" . "hourly")
|
("Hourly Air Quality Variables" . "hourly")
|
||||||
|
|
@ -137,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)"
|
||||||
"Ensemble Models")))))
|
"NOAA GFS & HRRR (U.S.)"
|
||||||
|
"MeteoFrance"
|
||||||
|
"ECMWF"
|
||||||
|
"JMA (Japan)"
|
||||||
|
"MET (Norway)"
|
||||||
|
"GEM (Canada)"
|
||||||
|
"BOM (Australia)"
|
||||||
|
"CMA (China)"
|
||||||
|
"Historical Weather"
|
||||||
|
"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."
|
||||||
|
|
@ -151,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)
|
||||||
|
|
@ -183,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 (opt) (cons (dom-attr opt 'value)
|
(lambda (item)
|
||||||
(biome-api-parse--fix-string
|
(not (equal (cdr item)
|
||||||
(dom-texts opt))))
|
"- (default)")))
|
||||||
(dom-by-tag elem 'option)))))
|
(mapcar
|
||||||
|
(lambda (opt) (cons (dom-attr opt 'value)
|
||||||
|
(biome-api-parse--fix-string
|
||||||
|
(dom-texts opt))))
|
||||||
|
(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))
|
||||||
|
|
@ -244,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))
|
||||||
|
|
@ -255,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)))))
|
||||||
|
|
||||||
|
|
@ -306,23 +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))))
|
||||||
|
;; Likewise with the 15-minutely section.
|
||||||
|
(when-let* ((15-minutely-data (biome-api-parse--postprocess-extract-section
|
||||||
|
sections "15-minutely" t))
|
||||||
|
(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
|
||||||
|
sections "location and time" t)))
|
||||||
|
(setf (alist-get :name location-data)
|
||||||
|
"Select Coordinates and Time"))
|
||||||
|
|
||||||
;; Add settings
|
;; Add settings
|
||||||
(when-let ((settings-data (biome-api-parse--postprocess-extract-section
|
(cl-loop
|
||||||
sections "settings")))
|
for (section-name . add-vars) in biome-api-parse--add-settings
|
||||||
(cl-loop for var in biome-api-parse--add-settings
|
do (when-let ((settings-data (biome-api-parse--postprocess-extract-section
|
||||||
if (member name (alist-get :pages var))
|
sections section-name)))
|
||||||
do (push (copy-tree (alist-get :param var))
|
(cl-loop for var in add-vars
|
||||||
(alist-get :fields (cdr settings-data))))
|
if (member name (alist-get :pages var))
|
||||||
;; Fix forecast_days for Flood API
|
do (setf (alist-get :fields (cdr settings-data))
|
||||||
(when (equal name "Flood")
|
(cons (copy-tree (alist-get :param var))
|
||||||
(let ((forecast-days (alist-get "forecast_days"
|
(alist-get :fields (cdr settings-data)))))
|
||||||
(alist-get :fields (cdr settings-data))
|
;; Fix forecast_days for Flood API
|
||||||
nil nil #'equal)))
|
(when (equal name "Flood")
|
||||||
(setf (alist-get :max forecast-days) 210))))
|
(when-let ((forecast-days (alist-get "forecast_days"
|
||||||
|
(alist-get :fields (cdr settings-data))
|
||||||
|
nil nil #'equal)))
|
||||||
|
(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
|
||||||
|
|
@ -403,8 +515,14 @@ fields attributes as cdr:
|
||||||
"Parse one page from open-meteo API docs.
|
"Parse one page from open-meteo API docs.
|
||||||
|
|
||||||
DATUM is an element of `biome-api-parse--urls'."
|
DATUM is an element of `biome-api-parse--urls'."
|
||||||
(browse-url (alist-get :url datum))
|
|
||||||
(let* ((html (read-string "Enter HTML string: "))
|
(let* ((html (or (gethash (alist-get :url datum) biome-api-parse--htmls)
|
||||||
|
(progn
|
||||||
|
(browse-url (alist-get :url datum))
|
||||||
|
(let ((html (read-string "Enter HTML string: ")))
|
||||||
|
(puthash (alist-get :url datum) html
|
||||||
|
biome-api-parse--htmls)
|
||||||
|
html))))
|
||||||
(parsed (biome-api-parse--page html (alist-get :name datum))))
|
(parsed (biome-api-parse--page html (alist-get :name datum))))
|
||||||
(setf (alist-get (alist-get :name datum)
|
(setf (alist-get (alist-get :name datum)
|
||||||
biome-api-parse--data nil nil #'equal)
|
biome-api-parse--data nil nil #'equal)
|
||||||
|
|
@ -421,7 +539,7 @@ DATUM is an element of `biome-api-parse--urls'."
|
||||||
seq-uniq
|
seq-uniq
|
||||||
(seq-sort #'string-lessp)))
|
(seq-sort #'string-lessp)))
|
||||||
|
|
||||||
(defun biome-api-parse--generate ()
|
(defun biome-api-parse--generate (&optional arg)
|
||||||
"Generate `biome-api-data' and `biome-api-timezones' constants.
|
"Generate `biome-api-data' and `biome-api-timezones' constants.
|
||||||
|
|
||||||
This function does two things:
|
This function does two things:
|
||||||
|
|
@ -432,14 +550,20 @@ Unfortunately, the HTML pages have accordions that are dynamically
|
||||||
loaded, so we need to manually load them in the browser, expand the
|
loaded, so we need to manually load them in the browser, expand the
|
||||||
accordions and copy the HTML source.
|
accordions and copy the HTML source.
|
||||||
|
|
||||||
|
HTML strings are cached in `biome-api-parse--htmls'. Set ARG to
|
||||||
|
non-nil to reset.
|
||||||
|
|
||||||
The function prints the generated constants to a new buffer. Save
|
The function prints the generated constants to a new buffer. Save
|
||||||
them to biome-api-data.el."
|
them to biome-api-data.el."
|
||||||
(interactive)
|
(interactive "P")
|
||||||
(setq biome-api-parse--data nil)
|
(setq biome-api-parse--data nil)
|
||||||
|
(when arg
|
||||||
|
(setq biome-api-parse--htmls (make-hash-table :test #'equal)))
|
||||||
(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
|
||||||
|
|
|
||||||
15
biome-api.el
15
biome-api.el
|
|
@ -30,15 +30,20 @@
|
||||||
|
|
||||||
(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")
|
||||||
|
("Historical Weather (on this day)"
|
||||||
|
. "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")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
104
biome-multi.el
104
biome-multi.el
|
|
@ -29,6 +29,7 @@
|
||||||
(require 'transient)
|
(require 'transient)
|
||||||
|
|
||||||
(require 'biome-query)
|
(require 'biome-query)
|
||||||
|
(require 'biome-api-parse)
|
||||||
|
|
||||||
;; XXX Recursive imports T_T
|
;; XXX Recursive imports T_T
|
||||||
(declare-function biome-preset "biome")
|
(declare-function biome-preset "biome")
|
||||||
|
|
@ -287,5 +288,108 @@ as it is necessary for `biome-grid'."
|
||||||
unit)))))
|
unit)))))
|
||||||
(multi . ,(biome-multi--join-results queries query-names vars-mapping results))))))
|
(multi . ,(biome-multi--join-results queries query-names vars-mapping results))))))
|
||||||
|
|
||||||
|
(defun biome-multi--history-section ()
|
||||||
|
"Create a section for `biome-multi-history'.
|
||||||
|
|
||||||
|
This is based on the Historical Weather section."
|
||||||
|
(let* ((history-params
|
||||||
|
(copy-tree (alist-get "Historical Weather" biome-api-data
|
||||||
|
nil nil #'equal)))
|
||||||
|
(time-section (biome-api-parse--postprocess-extract-section
|
||||||
|
(alist-get :sections history-params)
|
||||||
|
"coordinates and time"))
|
||||||
|
(current-year (decoded-time-year (decode-time))))
|
||||||
|
(push '("day_of_year" . ((:name . "Day of Year")
|
||||||
|
(:type . date)))
|
||||||
|
(alist-get :fields time-section))
|
||||||
|
(push
|
||||||
|
`("end_year" . ((:name . "End year")
|
||||||
|
(:type . number)
|
||||||
|
(:min . 1940)
|
||||||
|
(:max . ,current-year)))
|
||||||
|
(alist-get :fields time-section))
|
||||||
|
(push
|
||||||
|
`("start_year" . ((:name . "Start year")
|
||||||
|
(:type . number)
|
||||||
|
(:min . 1940)
|
||||||
|
(:max . ,current-year)))
|
||||||
|
(alist-get :fields time-section))
|
||||||
|
(setf (alist-get :name history-params)
|
||||||
|
"Historical Weather (on this day)")
|
||||||
|
(setf (alist-get :fields time-section)
|
||||||
|
(seq-filter
|
||||||
|
(lambda (elem)
|
||||||
|
(not (member (car elem) '("start_date" "end_date"))))
|
||||||
|
(alist-get :fields time-section)))
|
||||||
|
history-params))
|
||||||
|
|
||||||
|
(defun biome-multi-history--prepare-queries (query)
|
||||||
|
"Create queries for `biome-multi-history'.
|
||||||
|
|
||||||
|
QUERY is a query as defined by `biome-query-current', prepared like
|
||||||
|
for the normal Historical Weather section but with the following
|
||||||
|
added fields:
|
||||||
|
- start_year (number from 1940 to current)
|
||||||
|
- end_year (number from 1940 to current)
|
||||||
|
- day_of_year (timestamp)."
|
||||||
|
(let ((start-year (alist-get "start_year"
|
||||||
|
(alist-get :params query)
|
||||||
|
nil nil #'equal))
|
||||||
|
(end-year (alist-get "end_year"
|
||||||
|
(alist-get :params query)
|
||||||
|
nil nil #'equal))
|
||||||
|
(day-of-year (alist-get "day_of_year"
|
||||||
|
(alist-get :params query)
|
||||||
|
nil nil #'equal)))
|
||||||
|
(unless (and start-year end-year day-of-year)
|
||||||
|
(user-error "Set Start Year, End Year and Day of Year"))
|
||||||
|
(cl-loop with current-date = (decode-time (seconds-to-time day-of-year))
|
||||||
|
for year from start-year to end-year
|
||||||
|
for date = (copy-tree current-date)
|
||||||
|
do (setf (decoded-time-year date) year)
|
||||||
|
for time = (time-convert (encode-time date) 'integer)
|
||||||
|
for year-query = (copy-tree query)
|
||||||
|
do (setf (alist-get :params year-query)
|
||||||
|
(seq-filter (lambda (elem)
|
||||||
|
(not
|
||||||
|
(member
|
||||||
|
(car elem)
|
||||||
|
'("day_of_year" "end_year" "start_year"))))
|
||||||
|
(alist-get :params year-query)))
|
||||||
|
do (push (cons "start_date" (- time (% time (* 60 60 24))))
|
||||||
|
(alist-get :params year-query))
|
||||||
|
do (push (cons "end_date" (- time (% time (* 60 60 24))))
|
||||||
|
(alist-get :params year-query))
|
||||||
|
collect year-query)))
|
||||||
|
|
||||||
|
(defun biome-multi--history-query (callback)
|
||||||
|
"Get historical weather data on a particular day.
|
||||||
|
|
||||||
|
CALLBACK is called with a list of queries, one per day."
|
||||||
|
(interactive (list nil))
|
||||||
|
(let ((params (biome-multi--history-section)))
|
||||||
|
(setq biome-query--callback
|
||||||
|
(lambda (query)
|
||||||
|
(let ((queries (biome-multi-history--prepare-queries query)))
|
||||||
|
(when (y-or-n-p (format "Send %s requests to the API?"
|
||||||
|
(length queries)))
|
||||||
|
(funcall callback queries)))))
|
||||||
|
(biome-query--section-open-params params)))
|
||||||
|
|
||||||
|
(defun biome-multi--concat-results (queries results)
|
||||||
|
"Concat RESULTS from multiple Open Meteo responses.
|
||||||
|
|
||||||
|
QUERIES is a list of forms as defined by `biome-query-current'. Each
|
||||||
|
query is assumed to have the same variables. RESULTS is a list of
|
||||||
|
responses from Open Meteo."
|
||||||
|
(let ((group (intern (alist-get :group (car queries)))))
|
||||||
|
(cl-loop for result in (cdr results)
|
||||||
|
do (cl-loop
|
||||||
|
for (var . values) in (alist-get group result)
|
||||||
|
do (setf (alist-get var (alist-get group (car results)))
|
||||||
|
(vconcat (alist-get var (alist-get group (car results)))
|
||||||
|
values))))
|
||||||
|
(car results)))
|
||||||
|
|
||||||
(provide 'biome-multi)
|
(provide 'biome-multi)
|
||||||
;;; biome-multi.el ends here
|
;;; biome-multi.el ends here
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@
|
||||||
|
|
||||||
;; XXX Recursive imports T_T
|
;; XXX Recursive imports T_T
|
||||||
(declare-function biome-preset "biome")
|
(declare-function biome-preset "biome")
|
||||||
(declare-function biome-multi "biome")
|
(declare-function biome-multi "biome-multi")
|
||||||
|
(declare-function biome-multi-history "biome-multi")
|
||||||
|
|
||||||
(defcustom biome-query-max-fields-in-row 20
|
(defcustom biome-query-max-fields-in-row 20
|
||||||
"Maximum number of fields in a row."
|
"Maximum number of fields in a row."
|
||||||
|
|
@ -89,7 +90,10 @@ The format is: (name latitude longitude)."
|
||||||
:type 'string
|
:type 'string
|
||||||
:group 'biome)
|
:group 'biome)
|
||||||
|
|
||||||
(defconst biome-query-groups '("daily" "hourly" "minutely_15" "hourly")
|
(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")
|
||||||
"Name of groups.
|
"Name of groups.
|
||||||
|
|
||||||
A group is a mutually exclusive choice. E.g. in the \"Weather
|
A group is a mutually exclusive choice. E.g. in the \"Weather
|
||||||
|
|
@ -100,7 +104,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")
|
||||||
|
|
@ -204,7 +210,7 @@ QUERY is a form as defined by `transient-define-prefix'."
|
||||||
(setq lat (cdr item)))
|
(setq lat (cdr item)))
|
||||||
((equal (car item) "longitude")
|
((equal (car item) "longitude")
|
||||||
(setq lon (cdr item)))
|
(setq lon (cdr item)))
|
||||||
((member (car item) '("end_date" "start_date"))
|
((member (car item) '("end_date" "start_date" "day_of_year"))
|
||||||
(push
|
(push
|
||||||
(format "%s: %s" (propertize
|
(format "%s: %s" (propertize
|
||||||
(biome-query--get-header (car item) var-names)
|
(biome-query--get-header (car item) var-names)
|
||||||
|
|
@ -850,7 +856,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 +885,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)
|
||||||
|
|
@ -1018,18 +1028,27 @@ SECTION is a form as defined in `biome-api-parse--page'."
|
||||||
(:parents . ,parents))))
|
(:parents . ,parents))))
|
||||||
(put 'biome-query-section 'transient--layout nil)))
|
(put 'biome-query-section 'transient--layout nil)))
|
||||||
|
|
||||||
|
(defun biome-query--section-open-params (params)
|
||||||
|
"Open section defined by PARAMS in `biome-query--section'.
|
||||||
|
|
||||||
|
PARAMS is a form as defiend by `biome-api-data'."
|
||||||
|
(setq biome-query--current-section params)
|
||||||
|
(when (and biome-query-current
|
||||||
|
(not (equal (alist-get :name params)
|
||||||
|
(alist-get :name biome-query-current))))
|
||||||
|
(setq biome-query-current nil))
|
||||||
|
(unless biome-query-current
|
||||||
|
(biome-query--reset-report))
|
||||||
|
(funcall-interactively #'biome-query--section params))
|
||||||
|
|
||||||
(defun biome-query--section-open (name)
|
(defun biome-query--section-open (name)
|
||||||
"Open section NAME in `biome-query--section'."
|
"Open section NAME in `biome-query--section'."
|
||||||
(let ((params (alist-get name biome-api-data nil nil #'equal)))
|
(let ((params (alist-get name biome-api-data nil nil #'equal)))
|
||||||
(unless params
|
(cond
|
||||||
(error "No such section: %s" name))
|
((equal name "Historical Weather (on this day)")
|
||||||
(setq biome-query--current-section params)
|
(biome-multi-history))
|
||||||
(when (and biome-query-current
|
(params (biome-query--section-open-params params))
|
||||||
(not (equal name (alist-get :name biome-query-current))))
|
(t (error "No such section: %s" name)))))
|
||||||
(setq biome-query-current nil))
|
|
||||||
(unless biome-query-current
|
|
||||||
(biome-query--reset-report))
|
|
||||||
(funcall-interactively #'biome-query--section params)))
|
|
||||||
|
|
||||||
(transient-define-prefix biome-query (callback)
|
(transient-define-prefix biome-query (callback)
|
||||||
["Open Meteo Data"
|
["Open Meteo Data"
|
||||||
|
|
@ -1044,11 +1063,15 @@ SECTION is a form as defined in `biome-api-parse--page'."
|
||||||
(lambda () (interactive)
|
(lambda () (interactive)
|
||||||
(biome-query--section-open ,name))
|
(biome-query--section-open ,name))
|
||||||
:transient transient--do-stack))))]
|
:transient transient--do-stack))))]
|
||||||
|
["Aggregate Data"
|
||||||
|
:class transient-column
|
||||||
|
("i" "Historical Weather (on this day)" biome-multi-history
|
||||||
|
:transient transient--do-stack)
|
||||||
|
("u" "Join multiple queries" biome-multi :transient transient--do-stack)]
|
||||||
["Actions"
|
["Actions"
|
||||||
:class transient-row
|
:class transient-row
|
||||||
("r" "Resume" biome-resume :transient transient--do-replace)
|
("r" "Resume" biome-resume :transient transient--do-stack)
|
||||||
("p" "Preset" biome-preset :transient transient--do-stack)
|
("p" "Preset" biome-preset :transient transient--do-stack)
|
||||||
("u" "Join multiple queries" biome-multi :transient transient--do-stack)
|
|
||||||
("q" "Quit" transient-quit-one)]
|
("q" "Quit" transient-quit-one)]
|
||||||
(interactive (list nil))
|
(interactive (list nil))
|
||||||
(unless callback
|
(unless callback
|
||||||
|
|
|
||||||
14
biome.el
14
biome.el
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
;; Author: Korytov Pavel <thexcloud@gmail.com>
|
;; Author: Korytov Pavel <thexcloud@gmail.com>
|
||||||
;; Maintainer: Korytov Pavel <thexcloud@gmail.com>
|
;; Maintainer: Korytov Pavel <thexcloud@gmail.com>
|
||||||
;; Version: 0.1.0
|
;; Version: 0.3.0
|
||||||
;; Package-Requires: ((emacs "27.1") (transient "0.3.7") (ct "0.2") (request "0.3.3") (compat "29.1.4.1"))
|
;; Package-Requires: ((emacs "27.1") (transient "0.3.7") (ct "0.2") (request "0.3.3") (compat "29.1.4.1"))
|
||||||
;; Homepage: https://github.com/SqrtMinusOne/biome
|
;; Homepage: https://github.com/SqrtMinusOne/biome
|
||||||
;; Published-At: 2023-07-22
|
;; Published-At: 2023-07-22
|
||||||
|
|
@ -106,6 +106,18 @@ preset definition\" in `biome' or `biome-multi'."
|
||||||
(let ((merged (biome-multi--merge queries results)))
|
(let ((merged (biome-multi--merge queries results)))
|
||||||
(funcall biome-frontend (nth 0 merged) (nth 1 merged))))))))
|
(funcall biome-frontend (nth 0 merged) (nth 1 merged))))))))
|
||||||
|
|
||||||
|
(defun biome-multi-history ()
|
||||||
|
"Get historical weather data on a particular day."
|
||||||
|
(interactive)
|
||||||
|
(funcall-interactively
|
||||||
|
#'biome-multi--history-query
|
||||||
|
(lambda (queries)
|
||||||
|
(biome-api-get-multiple
|
||||||
|
queries
|
||||||
|
(lambda (queries results)
|
||||||
|
(let ((concat-results (biome-multi--concat-results queries results)))
|
||||||
|
(funcall biome-frontend (car queries) concat-results)))))))
|
||||||
|
|
||||||
(defmacro biome-def-preset (name params)
|
(defmacro biome-def-preset (name params)
|
||||||
"Declare a query preset.
|
"Declare a query preset.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue