diff --git a/biome-api.el b/biome-api.el index 7f9103f..20de313 100644 --- a/biome-api.el +++ b/biome-api.el @@ -40,6 +40,8 @@ ("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 (on this day)" + . "https://archive-api.open-meteo.com/v1/archive") ("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") diff --git a/biome-multi.el b/biome-multi.el index 587015a..06efdd6 100644 --- a/biome-multi.el +++ b/biome-multi.el @@ -29,6 +29,7 @@ (require 'transient) (require 'biome-query) +(require 'biome-api-parse) ;; XXX Recursive imports T_T (declare-function biome-preset "biome") @@ -287,5 +288,108 @@ as it is necessary for `biome-grid'." unit))))) (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) ;;; biome-multi.el ends here diff --git a/biome-query.el b/biome-query.el index 3882207..36f7eb8 100644 --- a/biome-query.el +++ b/biome-query.el @@ -41,7 +41,8 @@ ;; XXX Recursive imports T_T (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 "Maximum number of fields in a row." @@ -209,7 +210,7 @@ QUERY is a form as defined by `transient-define-prefix'." (setq lat (cdr item))) ((equal (car item) "longitude") (setq lon (cdr item))) - ((member (car item) '("end_date" "start_date")) + ((member (car item) '("end_date" "start_date" "day_of_year")) (push (format "%s: %s" (propertize (biome-query--get-header (car item) var-names) @@ -1027,18 +1028,27 @@ SECTION is a form as defined in `biome-api-parse--page'." (:parents . ,parents)))) (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) "Open section NAME in `biome-query--section'." (let ((params (alist-get name biome-api-data nil nil #'equal))) - (unless params - (error "No such section: %s" name)) - (setq biome-query--current-section params) - (when (and biome-query-current - (not (equal name (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))) + (cond + ((equal name "Historical Weather (on this day)") + (biome-multi-history)) + (params (biome-query--section-open-params params)) + (t (error "No such section: %s" name))))) (transient-define-prefix biome-query (callback) ["Open Meteo Data" @@ -1053,11 +1063,15 @@ SECTION is a form as defined in `biome-api-parse--page'." (lambda () (interactive) (biome-query--section-open ,name)) :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" :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) - ("u" "Join multiple queries" biome-multi :transient transient--do-stack) ("q" "Quit" transient-quit-one)] (interactive (list nil)) (unless callback diff --git a/biome.el b/biome.el index c44a742..10fcaa0 100644 --- a/biome.el +++ b/biome.el @@ -106,6 +106,18 @@ preset definition\" in `biome' or `biome-multi'." (let ((merged (biome-multi--merge queries results))) (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) "Declare a query preset.