From a2ad653df4ac4f3260541f1b0b24efbf8ce9d1ef Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Mon, 2 Oct 2023 16:53:56 +1100 Subject: First commit. Serving Victoria weather over localhost:9000 --- bom.el | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 bom.el (limited to 'bom.el') diff --git a/bom.el b/bom.el new file mode 100644 index 0000000..710aa4d --- /dev/null +++ b/bom.el @@ -0,0 +1,151 @@ +;;; bom.el -- Australian weather forecast from the Bureau -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; Author: Yuchen Pei +;; Package-Requires: ((emacs "28.2") (web-server "0.0.2")) + +;; This file is part of bom.el. + +;; bom.el is free software: you can redistribute it and/or modify it +;; under the terms of the GNU Affero General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. + +;; bom.el is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;; Public License for more details. + +;; You should have received a copy of the GNU Affero General Public +;; License along with bom.el. If not, see . + +;;; Commentary: + +;; Australian weather forecast from the Bureau. A client that +;; downloads weather forecast and serves them using emacs-web-server. + +;;; Code: + +(require 'hierarchy) +(require 'web-server) + +(defvar bom-state-files + '((act . "IDN11060") + (nsw . "IDN11060") + (nt . "IDD10207") + (qld . "IDQ11295") + (sa . "IDS10044") + (tas . "IDT16710") + (vic . "IDV10753") + (wa . "IDW14199")) + "Alist of states and territories and their corresponding filenames on the BOM FTP server, see .") + +(defun bom-api-vic () + "Get Victoria weather from the BOM FTP." + (with-current-buffer + (find-file-noselect + "/ftp:anonymous@ftp.bom.gov.au:/anon/gen/fwo/IDV10753.xml") + (libxml-parse-xml-region (point-min) (point-max)))) + +(defun bom-get-areas (resp) + "Given an API response RESP, get all areas." + (dom-children (dom-by-tag resp 'forecast))) + +(defvar bom-areas nil "List of areas with forecasts.") + +(defvar bom-area-hierarchy (hierarchy-new) + "Hierarchy of areas with forecasts.") + +(defvar bom-server nil "The BOM server object.") + +(defun bom-area-parent (area) + "Search `bom-areas' for the parent of AREA. + +Used as parentfn for hierarchy." + (let ((parent-aac (dom-attr area 'parent-aac))) + (cl-find-if + (lambda (area) + (equal (dom-attr area 'aac) parent-aac)) + bom-areas))) + +(defun bom-format-area (area level) + "Format an AREA of LEVEL." + (format "%s %s%s\n" + (make-string level ?*) + (dom-attr area 'description) + (if (dom-children area) + (concat "\n" (bom-format-forecasts (dom-children area))) + ""))) + +(defun bom-area-lessp (a1 a2) + "Compare two areas A1 and A2 based on their AAC identifier." + (string-lessp (dom-attr a1 'aac) (dom-attr a2 'aac))) + +(defun bom-format-forecasts (forecasts) + "Format a list of FORECASTS." + (mapconcat 'bom-format-forecast forecasts "\n")) + +(defun bom-format-forecast (forecast) + "Format a FORECAST period." + (let ((date (substring (dom-attr forecast 'start-time-local) 5 10)) + (data (dom-children forecast)) + (min-temp) (max-temp) (precis) (prec-prob)) + (dolist (item data) + (pcase (dom-attr item 'type) + ("air_temperature_minimum" (setq min-temp (dom-text item))) + ("air_temperature_maximum" (setq max-temp (dom-text item))) + ("probability_of_precipitation" (setq prec-prob (dom-text item))) + ("precis" (setq precis (dom-text item))))) + (format "- %s :: %s %s %s" + date prec-prob precis (bom-format-temp-range min-temp max-temp)))) + +(defun bom-format-temp-range (min-temp max-temp) + "Format a temperature range from MIN-TEMP to MAX-TEMP." + (if (or min-temp max-temp) + (format "%s - %s" + (if min-temp (concat min-temp "C") "") + (if max-temp (concat max-temp "C") "")))) + +(defun bom-format-areas-org (areas) + "Format hierarchy of AREAS with forecasts." + (concat "#+title: Victoria weather forecast\n\n" + (string-join + (hierarchy-map 'bom-format-area bom-area-hierarchy 1) + "\n"))) + +(defun bom-format-areas-html (areas) + "Format hierarchy of AREAS with forecasts to html." + (with-temp-buffer + (insert (bom-format-areas-org areas)) + (org-export-as 'html))) + +(defun bom-get-vic-forecasts () + "Call BOM API and return hierarchy of VIC areas with forecasts." + (let ((areas (hierarchy-new))) + (hierarchy-add-trees + bom-area-hierarchy (bom-get-areas (bom-api-vic)) 'bom-area-parent) + (hierarchy-sort bom-area-hierarchy 'bom-area-lessp) + areas)) + +;;;###autoload +(defun bom-start () + "Start serving weather forecasts." + (when bom-server (bom-stop)) + (setq bom-server + (ws-start + (lambda (request) + (with-slots (process headers) request + (ws-response-header process 200 '("Content-type" . "text/html")) + (process-send-string + process + (bom-format-areas-html + (bom-get-vic-forecasts))))) + 9000))) + +(defun bom-stop () + "Stop serving weather forecasts." + (ws-stop bom-server)) + +(provide 'bom) +;;; bom.el ends here -- cgit v1.2.3